File-Based Routing
Astro uses file-based routing: every file you place inside src/pages automatically becomes a route in your site. There is no router to install, no route table to maintain, and no boilerplate configuration. The shape of your src/pages directory is your URL structure, so you can reason about your site map just by looking at the file tree. This convention keeps projects predictable and makes adding a new page as simple as creating a new file.
How files map to URLs
When Astro builds your project, it walks src/pages and turns each supported file into an HTML page. The path of the file (minus the src/pages prefix and the extension) becomes the URL path. Folders create nested URL segments, and a file named index maps to the root of its folder.
src/pages/
├── index.astro → /
├── about.astro → /about
├── contact.md → /contact
└── blog/
├── index.astro → /blog
└── first-post.md → /blog/first-post
Astro treats several file types as page routes:
| File type | Extension | Becomes |
|---|---|---|
| Astro component | .astro | An HTML page rendered from the component |
| Markdown / MDX | .md, .mdx | An HTML page rendered from the content |
| HTML | .html | A passthrough HTML page |
| Endpoint | .js, .ts | A non-HTML route (JSON, RSS, sitemap, etc.) |
Only files inside
src/pagesproduce routes. Components insrc/components, layouts insrc/layouts, and anything else outsidesrc/pagesare never exposed as URLs — they exist purely to be imported.
A minimal page
A page is just an Astro component. The optional fenced --- block at the top is the component script, which runs on the server at build (or request) time. Everything below it is the HTML template.
---
// src/pages/about.astro
const company = "DevCraftly";
const founded = "2015";
---
<html lang="en">
<head>
<title>About {company}</title>
</head>
<body>
<h1>About {company}</h1>
<p>Building developer tooling since {founded}.</p>
</body>
</html>
Visiting /about renders this page as static HTML. By default Astro ships zero JavaScript to the browser — the page above is pure HTML and CSS unless you opt into interactivity with an island.
Index routes and clean URLs
A file named index resolves to its containing directory’s URL. This lets you choose between two equally valid layouts for the same /blog route:
src/pages/blog.astro → /blog
src/pages/blog/index.astro → /blog
The nested blog/index.astro form is usually preferable once a section grows, because related child routes (blog/post-one.astro, blog/archive.astro) live alongside it in the same folder.
By default, Astro generates a directory with an index.html for each page (for example /about/index.html), which servers expose as the clean URL /about. You can control this with the build.format option in astro.config.mjs:
// astro.config.mjs
import { defineConfig } from "astro/config";
export default defineConfig({
build: {
// "directory" → /about/index.html (default, clean URLs)
// "file" → /about.html
format: "directory",
// Append a trailing slash consistently
trailingSlash: "always",
},
});
Linking between pages
Because routes are plain URLs, you link to them with a standard anchor tag — no special <Link> component is required.
---
// src/pages/index.astro
---
<nav>
<a href="/">Home</a>
<a href="/about">About</a>
<a href="/blog">Blog</a>
</nav>
For a project deployed under a sub-path (a base other than /), prefix links with import.meta.env.BASE_URL so they resolve correctly in every environment.
Static vs. on-demand rendering
By default every route is prerendered to static HTML at build time — fast, cacheable, and CDN-friendly. When you add a server adapter and enable on-demand rendering, the same files can run per-request instead, which is what powers dynamic data, form handling, and authenticated pages. The routing convention is identical in both modes; only when the file runs changes.
// astro.config.mjs
import { defineConfig } from "astro/config";
import node from "@astrojs/node";
export default defineConfig({
output: "server", // enable on-demand rendering
adapter: node({ mode: "standalone" }),
});
You can keep individual pages static even in server mode by exporting export const prerender = true; from that page’s component script.
Best practices
- Mirror your information architecture in the folder structure — the file tree should read like a site map.
- Use nested
index.astrofiles for section landing pages so child routes stay grouped together. - Keep reusable markup in
src/componentsandsrc/layouts; only put true pages insrc/pages. - Prefer static prerendering and reach for on-demand rendering only on routes that genuinely need per-request logic.
- Pick one
trailingSlashpolicy and apply it consistently to avoid duplicate-URL and redirect issues. - Reserve
.js/.tsfiles insrc/pagesfor endpoints (JSON, RSS, sitemaps), not for UI pages.