Skip to content

Project Structure

A freshly scaffolded Astro project looks familiar if you know Next.js, but several conventions are subtly different. The most important: src/pages/ is the only directory Astro treats as special for routing — everything else is up to you.

React
my-next-app/
├── src/
│ ├── app/ # App Router pages (Next 13+)
│ │ ├── page.tsx # → /
│ │ └── about/
│ │ └── page.tsx # → /about
│ ├── components/ # Shared React components
│ └── lib/ # Utilities
├── public/ # Static assets (served at /)
├── next.config.mjs # Framework config
└── package.json
Astro
my-astro-app/
├── src/
│ ├── pages/ # File-based routes (special)
│ │ ├── index.astro # → /
│ │ └── about.astro # → /about
│ ├── components/ # .astro and framework components
│ ├── layouts/ # Layout wrappers (Astro convention)
│ └── content/ # Content Collections (MDX, YAML, JSON)
├── public/ # Static assets (served at /)
├── astro.config.mjs # Framework config
└── package.json

Key differences to notice:

  1. src/pages/ — not src/app/. Astro uses the Pages Router mental model: a file at src/pages/about.astro renders at /about. No special page.tsx wrapper needed.
  2. src/layouts/ is a convention, not a framework rule. Astro does not enforce a layouts/ directory — but every Astro project uses one by convention because layout components are a first-class pattern (covered in the Layouts module).
  3. src/content/ is for Content Collections — structured content like MDX blog posts, team member YAML files, or JSON data. Astro provides a typed API to query it. You would use getStaticPaths + getEntry instead of getStaticProps + a file read.
  4. astro.config.mjs replaces next.config.mjs. Same idea — framework config, integrations, Vite overrides.

Both Next.js and Astro have a public/ directory. Files placed there are served at the root URL as-is — no bundling, no transforms. A file at public/logo.png is available at /logo.png.

The difference: in Next.js you reference images with the <Image> component for optimisation. In Astro you use a plain <img> tag (or Astro’s built-in image optimisation — import { Image } from 'astro:assets').

astro.config.mjs
import { defineConfig } from 'astro/config';
import react from '@astrojs/react';
import tailwind from '@astrojs/tailwind';
export default defineConfig({
site: 'https://my-site.com',
integrations: [react(), tailwind()],
// Vite config lives inside vite: {}
});

Compare to next.config.mjs — the shape is similar (a default export returning a config object) but the keys differ. Astro does not have experimental, rewrites, or redirects as top-level keys; those go through Vite or Astro middleware.

Astro
---
// src/pages/index.astro
// Frontmatter: runs at build/server time
const siteName = "My Astro Site";
const navLinks = [
  { href: "/", label: "Home" },
  { href: "/about", label: "About" },
  { href: "/blog", label: "Blog" },
];
---
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <title>{siteName}</title>
    <style>
      body { font-family: sans-serif; margin: 0; }
      nav { background: #1e1b4b; padding: 1rem 2rem; display: flex; gap: 1.5rem; }
      nav a { color: #c7d2fe; text-decoration: none; font-size: 0.9rem; }
      nav a:hover { color: #fff; }
      main { padding: 2rem; }
      .badge { background: #ede9fe; color: #4c1d95; padding: 0.2rem 0.6rem; border-radius: 4px; font-size: 0.75rem; }
    </style>
  </head>
  <body>
    <nav>
      {navLinks.map(link => (
        <a href={link.href}>{link.label}</a>
      ))}
    </nav>
    <main>
      <span class="badge">src/pages/index.astro</span>
      <h1>Welcome to {siteName}</h1>
      <p>This page lives at <code>src/pages/index.astro</code> and renders at <code>/</code>.</p>
      <p>Components go in <code>src/components/</code>, layouts in <code>src/layouts/</code>.</p>
    </main>
  </body>
</html>
Which directory in an Astro project determines the URL routes?
What is `src/content/` used for in Astro?
A file placed in `public/logo.png` is available at which URL?