
What is the Astro project folder structure?
The Astro project folder structure has two main directories: src/, which holds the code and content Astro processes (pages, components, layouts, styles, images and content), and public/, which holds files copied to the build untouched (favicons, robots.txt, fonts). At the project root sit package.json, astro.config.mjs and tsconfig.json. The only folder Astro strictly requires is src/pages/, because every file in it becomes a route. Everything else, such as src/components/ and src/layouts/, is a convention you're free to rename, although a handful of special files like src/content.config.ts and src/middleware.ts must live at specific paths.
Knowing which parts are rules and which are conventions makes it much easier to organise a growing project. In this article you'll learn what a new project contains, what each folder and root file is for, the special file names Astro looks for, how src/ and public/ differ in the build output, which folders are generated, and a structure that works well for larger sites. Examples come from projects built with Astro 7.4.2.
A Typical Astro Project
A blog-style project with a few pages, components and a content collection usually looks like this:
my-site/
├── public/
│ ├── favicon.svg
│ └── robots.txt
├── src/
│ ├── assets/
│ │ └── logo.svg
│ ├── components/
│ │ ├── Header.astro
│ │ └── LikeButton.jsx
│ ├── content/
│ │ └── blog/
│ │ ├── first-post.md
│ │ └── second-post.mdx
│ ├── layouts/
│ │ └── Base.astro
│ ├── pages/
│ │ ├── blog/
│ │ │ ├── [...id].astro
│ │ │ └── index.astro
│ │ ├── index.astro
│ │ └── rss.xml.js
│ ├── styles/
│ │ └── global.css
│ └── content.config.ts
├── astro.config.mjs
├── package.json
└── tsconfig.json
A fresh project from npm create astro@latest is smaller, with src/pages/index.astro, a layout, a component and an assets folder. You add the rest as you need it. Creating that starter project is covered in how to create a new Astro project from scratch.
The src/ Directory
src/ contains everything Astro builds. Files here are compiled, bundled and optimised: .astro components are rendered to HTML, CSS is scoped and minified, scripts are bundled and images are processed. Some of these files never reach the browser in their original form.
src/pages: The Only Required Folder
Every supported file in src/pages/ becomes a route on your site. This is Astro's file-based routing:
| File | URL |
|---|---|
src/pages/index.astro | / |
src/pages/about.astro | /about/ |
src/pages/blog/index.astro | /blog/ |
src/pages/blog/[...id].astro | /blog/first-post/, /blog/second-post/ |
src/pages/rss.xml.js | /rss.xml |
The supported page types are:
- .astro: Astro components rendered as pages.
- .md: Markdown files rendered as pages.
- .mdx: with the MDX integration installed.
- .html: plain HTML pages, with no Astro features.
- .js and .ts: endpoints that return a
Response, such as feeds, JSON APIs or generated files.
To keep a file inside src/pages/ without publishing it, prefix its name with an underscore. A file called src/pages/_notes.astro isn't recognised by the router and never appears in dist/. That's handy for page-specific helpers or work in progress. For the full routing rules, see Astro file-based routing.
src/components
Reusable pieces of UI live here by convention: Astro components (.astro) and framework components such as .jsx, .svelte or .vue. You import them with relative paths:
---
// src/components/Header.astro
import logo from '../assets/logo.svg';
---
<header>
<a href="/"><img src={logo.src} width={logo.width} height={logo.height} alt="Home" /></a>
</header>
Nothing about the folder name is special. Some teams group components by feature (src/components/blog/, src/components/shop/), others by type (ui/, sections/). Pick whichever is easier to navigate.
src/layouts
Layouts are Astro components that wrap pages with shared structure: the <html> element, <head> metadata, header and footer. They're ordinary components that render a <slot /> where the page content goes:
---
// src/layouts/Base.astro
import '../styles/global.css';
import Header from '../components/Header.astro';
const { title } = Astro.props;
---
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<title>{title}</title>
</head>
<body>
<Header />
<slot />
</body>
</html>
Again, src/layouts is a convention. Astro has no automatic layout files; you import a layout into each page that needs it. More patterns are in how to create reusable layouts in Astro.
src/styles
A common home for global CSS, Sass files or design tokens. Import a stylesheet from a layout or page and Astro bundles and optimises it. In the example above, global.css ended up inlined and minified in the page's <head>:
<style>body{max-width:70ch;margin:0 auto;font-family:system-ui,sans-serif}
</style>
Component-specific styles usually live in the component's own <style> tag instead, where they're scoped automatically.
src/assets
Images and other files you import from code. Importing an image gives you its URL and dimensions, and lets Astro hash the file name and optimise it with <Image /> or <Picture />:
<img src="/_astro/logo.Dm8K3lV8.svg" width="115" height="48" alt="Home">
The hashed name means browsers can cache the file indefinitely, and a new version gets a new URL.
src/content
Markdown, MDX, JSON or YAML content for content collections often lives in src/content/, but since Astro 6 that's only a convention. A collection's location is whatever you pass to its loader, so ./src/data/blog or even a folder outside src/ works just as well.
Special Files in src/
Most of src/ is yours to arrange, but Astro looks for a few files by name:
| File | Purpose |
|---|---|
src/content.config.ts | Defines build-time content collections, their loaders and schemas |
src/live.config.ts | Defines live content collections, fetched at request time |
src/middleware.ts (or src/middleware/index.ts) | Middleware that runs on every request |
src/actions/index.ts | Exports the server object of Astro Actions |
src/fetch.ts | Advanced routing entrypoint, reserved since Astro 7 |
src/env.d.ts | Optional, conventional place for custom type declarations |
src/fetch.ts deserves a note if you're upgrading. Astro 7 enables advanced routing by default and treats this file as a custom request handler that default-exports an object with a fetch() method. If your project already used src/fetch.ts for something else, such as an API client, rename it or set the fetchFile config option to null to disable the entrypoint.
The collection config looks like this:
// src/content.config.ts
import { defineCollection } from 'astro:content';
import { glob } from 'astro/loaders';
import { z } from 'astro/zod';
const blog = defineCollection({
loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/blog' }),
schema: z.object({
title: z.string(),
pubDate: z.coerce.date(),
}),
});
export const collections = { blog };
The older src/content/config.ts location and collections without a loader were removed in Astro 6. If you see them in an old tutorial, the project needs migrating; the temporary legacy.collectionsBackwardsCompat flag can keep an old project building while you do.
The public/ Directory
public/ is for files that shouldn't be processed. Astro copies them to the root of the build output exactly as they are, so public/robots.txt becomes /robots.txt and public/favicon.svg becomes /favicon.svg.
Typical contents:
- Favicons and app icons, including
favicon.icoandapple-touch-icon.png. - robots.txt, manifest.webmanifest and verification files for search consoles.
- Fonts you reference by absolute URL in CSS.
- Downloads such as PDFs.
- Third-party scripts loaded with
<script is:inline src="/script.js">.
Reference these files with absolute paths starting at the site root (/favicon.svg), not with imports. As a rule of thumb, CSS and JavaScript you write yourself belong in src/, because files in public/ aren't bundled, minified or optimised.
src/ vs public/ in the Build
Building the project above produces this dist/ folder:
dist/_astro/cover.CzJmK1Kz_9nDuo.webp
dist/_astro/logo.Dm8K3lV8.svg
dist/blog/first-post/index.html
dist/blog/index.html
dist/blog/second-post/index.html
dist/favicon.ico
dist/favicon.svg
dist/index.html
dist/robots.txt
dist/rss.xml
dist/sitemap-0.xml
dist/sitemap-index.xml
Everything from public/ sits at the root unchanged. Everything Astro processed from src/ either became an HTML page or went into dist/_astro/ with a content hash in its name, like the logo and the cover image converted to WebP. The _notes.astro page is absent because of its underscore prefix.
src/ | public/ | |
|---|---|---|
| Processed by Astro | Yes | No |
| File names | Hashed (logo.Dm8K3lV8.svg) | Unchanged |
| How you reference files | import | Absolute URL (/robots.txt) |
| Image optimisation | Yes | No |
| Best for | Components, styles, scripts, images | Favicons, robots.txt, fonts, downloads |
Root Files
package.json
Lists your dependencies and scripts. A new project has dev, build, preview and astro scripts, sets "type": "module" and declares "engines": { "node": ">=22.23.4" }. Astro's docs suggest putting dependencies in dependencies unless you have a specific reason to use devDependencies, since Astro needs them all at build time anyway.
astro.config.mjs
Your project configuration: integrations, the adapter, site, output, Markdown options, image settings, route caching and more. Astro accepts astro.config.js, astro.config.mjs and astro.config.ts. The recommendation is .mjs, or .ts if you want TypeScript in your config:
// astro.config.mjs
import { defineConfig } from 'astro/config';
import mdx from '@astrojs/mdx';
import sitemap from '@astrojs/sitemap';
export default defineConfig({
site: 'https://example.com',
integrations: [mdx(), sitemap()],
});
The full option list is covered in the Astro configuration guide.
tsconfig.json
TypeScript configuration, needed even in JavaScript projects so editors understand imports and Astro's virtual modules. New projects extend Astro's strict preset and include the generated types:
{
"extends": "astro/tsconfigs/strict",
"include": [".astro/types.d.ts", "**/*"],
"exclude": ["dist"]
}
The presets are base, strict and strictest. Content collections need strictNullChecks, which strict and strictest already enable.
Other Root Files
- .gitignore: excludes
dist/,.astro/,node_modules/,.envfiles and log files. - .vscode/: recommends the Astro extension and adds a launch configuration for the dev server.
- AGENTS.md: guidance for AI coding agents, created by
create-astrounless you pass--no-ai. - README.md: a short description of the project and its commands.
- .env: environment variables, which must never be committed.
Generated Folders
Three folders are created by tools and should stay out of version control:
- node_modules/: installed packages.
- dist/: the build output. It's recreated on every
astro build. With a server adapter, it's split intodist/client/for static assets anddist/server/for the server entry. - .astro/: generated types and content-layer data. In a project with a blog collection it contains files such as
types.d.ts,content.d.tsandcollections/blog.schema.json.
The generated types.d.ts is what gives you types for astro:content and other virtual modules:
/// <reference types="astro/client" />
/// <reference path="content.d.ts" />
If types for a new collection don't show up in your editor, run npx astro sync or restart the dev server to regenerate this folder.
Organising a Larger Project
As a site grows, a few habits keep the structure manageable:
- Group components by feature once you have more than a couple of dozen:
components/blog/,components/marketing/,components/ui/. - Keep pages thin. A page should mostly fetch data and compose layouts and components.
- Put shared logic in src/lib/ or src/utils/ for date formatting, API clients and helpers. Just don't call a file
src/fetch.ts. - Use import aliases if relative paths get long. Add
compilerOptions.pathsintsconfig.json, such as"@components/*": ["src/components/*"]. - Store content by collection: one folder per collection, with images next to the entries that use them.
FAQ: Astro Project Structure
Only src/pages is required, because it defines your routes. Folders such as components, layouts, styles and assets are conventions you can rename or reorganise freely.
Files in src/assets are imported, hashed and can be optimised by Astro. Files in public are copied unchanged to the site root and referenced by absolute URL. Use public for favicons, robots.txt and downloads, and src for images you display on pages.
In src/content.config.ts at the root of src. The old src/content/config.ts location was removed in Astro 6 (apart from a temporary backwards-compatibility flag), and every collection now needs a loader such as glob() or file().
Prefix the file or folder name with an underscore, for example src/pages/_helpers.ts. The router ignores it and it isn't written to dist.
No. It contains generated types and content data, is listed in the default .gitignore and is recreated by astro dev, astro build or astro sync.
Astro 7 reserves src/fetch.ts as the advanced routing entrypoint. If you used that file for something else, rename it or set the fetchFile option to null in your Astro config.
Conclusion
An Astro project is split between src/, where everything is processed, and public/, where files are copied as they are. Inside src/, only pages/ is mandatory and maps directly to your site's URLs; components, layouts, styles, assets and content folders are conventions that keep things tidy. A small set of special files, including content.config.ts, middleware.ts, actions/index.ts and, since Astro 7, fetch.ts, must live where Astro expects them.
At the root, package.json, astro.config.mjs and tsconfig.json define your dependencies, configuration and types, while dist/, .astro/ and node_modules/ are generated and stay out of Git. Learn those rules and the rest of the structure is yours to shape around how your team works.


