Skip to content

Routing

Astro uses file-based routing. A file in src/pages/ becomes a URL in the generated site. This keeps the route tree visible in the filesystem, which is one reason Astro feels straightforward for content sites.

The basic mapping is direct:

src/pages/index.astro -> /
src/pages/about.astro -> /about/
src/pages/blog/index.astro -> /blog/
src/pages/blog/post-1.astro -> /blog/post-1/

This model is easy to learn, but there are a few details worth understanding before a project grows: index routes, dynamic routes, rest parameters, content collections, and deployment base paths.

Static routes are the simplest case. Create a file, and Astro creates a page.

---
const title = 'About';
---
<main>
<h1>{title}</h1>
<p>This route is generated from src/pages/about.astro.</p>
</main>

This pattern is enough for hand-written pages such as about, pricing, contact, a docs landing page, or a small guide.

Use static routes when the page exists once and has a stable purpose. Do not make a route dynamic just because it could be expressed as data.

index.astro represents the root of its directory:

src/pages/index.astro -> /
src/pages/blog/index.astro -> /blog/
src/pages/docs/index.astro -> /docs/
src/pages/docs/install.astro -> /docs/install/

This is useful when a section needs both an overview page and child pages. The folder becomes the section, and index.astro becomes the section landing page.

A common docs pattern is:

src/pages/guides/index.astro
src/pages/guides/install.astro
src/pages/guides/deploy.astro

That gives readers a section home at /guides/ and direct task pages below it.

Dynamic routes use bracket syntax when part of the path is a variable:

src/pages/blog/[slug].astro

In static output mode, the build must know every generated route ahead of time. A dynamic route therefore exports getStaticPaths():

---
export function getStaticPaths() {
return [
{ params: { slug: 'first-post' } },
{ params: { slug: 'second-post' } },
];
}
const { slug } = Astro.params;
---
<main>
<h1>{slug}</h1>
</main>

This generates /blog/first-post/ and /blog/second-post/.

Hard-coded arrays are fine for a tiny example. Real content sites usually generate paths from content, a CMS, or another data source.

params decides the URL. props passes data to the page.

---
export function getStaticPaths() {
const products = [
{ slug: 'starter', name: 'Starter', price: '$19' },
{ slug: 'pro', name: 'Pro', price: '$49' },
];
return products.map((product) => ({
params: { slug: product.slug },
props: { product },
}));
}
const { product } = Astro.props;
---
<main>
<h1>{product.name}</h1>
<p>{product.price}</p>
</main>

This avoids looking up the same data again inside the template. It also keeps the route’s data flow visible: generate a path, pass the data, render the page.

Content collections live outside src/pages/, so collection entries do not become routes by themselves. A blog collection needs a dynamic route that reads entries and turns them into pages:

---
import { getCollection, render } from 'astro:content';
export async function getStaticPaths() {
const posts = await getCollection('blog');
return posts.map((post) => ({
params: { id: post.id },
props: { post },
}));
}
const { post } = Astro.props;
const { Content } = await render(post);
---
<main>
<h1>{post.data.title}</h1>
<Content />
</main>

With this file at src/pages/blog/[id].astro, an entry with the id first-post becomes /blog/first-post/.

For larger sites, normalize slugs deliberately. Decide whether the URL should use post.id, a frontmatter slug, date folders, language prefixes, or nested paths. Changing that decision later can mean redirects.

Astro supports rest parameters such as [...slug].astro. They can match more than one path segment:

src/pages/docs/[...slug].astro

A rest parameter can represent routes like:

/docs/
/docs/install/
/docs/deploy/cloudflare/

This pattern is useful for generated documentation trees, imported content with nested slugs, or compatibility routes. It should not be the first tool you reach for. A catch-all route can make a site harder to understand because one file now owns many possible URLs.

Use explicit routes while the structure is small. Reach for rest parameters when nested depth is part of the content model.

Starlight manages documentation routes through the docs content collection, so a page like this guide lives under /en/astro/routing/ rather than directly under src/pages/.

Astro routing still matters in a Starlight project. Root landing pages, custom non-doc pages, redirects, API routes, and deployment base paths all use Astro’s routing and configuration model.

For documentation sites, it helps to think in two layers:

LayerWhat owns it
Docs pagesStarlight docs collection
Custom pagessrc/pages/
Generated content routesDynamic routes in src/pages/
Deployment pathsite and base in Astro config

If a site is deployed at the domain root, links can usually assume /. If a site is deployed under a subpath, such as a GitHub Pages project site at https://user.github.io/repo/, the project needs a base value:

import { defineConfig } from 'astro/config';
export default defineConfig({
site: 'https://user.github.io',
base: '/repo/',
});

Base-path bugs often appear late because local development uses /. If links or assets work locally but break after deployment, check site, base, and hard-coded absolute links.

Prefer Astro-aware helpers and relative links where appropriate. When you do write absolute paths, remember whether the deployed site lives at / or under a subpath.

SymptomLikely cause
Dynamic route builds no pagesgetStaticPaths() returns an empty array or wrong params key
Page works in dev but 404s after deployStatic host is serving a different output path or base path
Content entry renders at the wrong URLRoute uses id but the desired URL is a custom slug
Catch-all route hides missing pages[...slug].astro is too broad
Links break only on GitHub Pagesbase is missing or hard-coded / paths ignore it
  • Keep route filenames boring and descriptive.
  • Use index.astro for section landing pages.
  • Use dynamic routes for content-driven pages, not for pages that only exist once.
  • Use rest parameters only when nested depth is part of the content model.
  • Decide slug rules before publishing a large content section.
  • Treat base as a deployment setting that must be checked before publishing.