Skip to content
Astro as routing 4 min read

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 typeExtensionBecomes
Astro component.astroAn HTML page rendered from the component
Markdown / MDX.md, .mdxAn HTML page rendered from the content
HTML.htmlA passthrough HTML page
Endpoint.js, .tsA non-HTML route (JSON, RSS, sitemap, etc.)

Only files inside src/pages produce routes. Components in src/components, layouts in src/layouts, and anything else outside src/pages are 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.astro files for section landing pages so child routes stay grouped together.
  • Keep reusable markup in src/components and src/layouts; only put true pages in src/pages.
  • Prefer static prerendering and reach for on-demand rendering only on routes that genuinely need per-request logic.
  • Pick one trailingSlash policy and apply it consistently to avoid duplicate-URL and redirect issues.
  • Reserve .js/.ts files in src/pages for endpoints (JSON, RSS, sitemaps), not for UI pages.
Last updated June 14, 2026
Was this helpful?