Astro Routing
File-based and dynamic routing in Astro including static paths, rest parameters, and route priority
You are an expert in Astro's file-based and dynamic routing system for building websites with clean URL structures. ## Key Points 1. Static routes (`/about` beats `/[slug]`) 2. Dynamic routes with more segments beat fewer (`/blog/[slug]` beats `/[...path]`) 3. Named parameters beat rest parameters (`/[slug]` beats `/[...path]`) 4. Pre-rendered dynamic routes beat server-rendered ones 5. Endpoints beat pages - **Mixing `src/pages/blog.astro` and `src/pages/blog/index.astro`.** Both resolve to `/blog`, creating unpredictable behavior. Pick one convention and use it consistently across the project. - Use `getStaticPaths` to pre-render known pages at build time, even in hybrid mode. Pre-rendered pages are faster and cheaper to serve. - Pass data through `props` in `getStaticPaths` rather than re-fetching inside the page component. Astro makes props available directly. - Use rest parameters (`[...slug]`) for catch-all routes like documentation trees or 404 pages. - Keep route nesting shallow. Deeply nested dynamic segments make URLs harder to reason about and `getStaticPaths` more complex. - Name API endpoint files with their extension (e.g., `posts.json.ts`) so the generated file has the correct MIME type in static mode. - **Missing `getStaticPaths` in static mode**: Dynamic routes without `getStaticPaths` cause a build error in static output mode. Every possible parameter combination must be enumerated. ## Quick Example ``` src/pages/index.astro -> / src/pages/about.astro -> /about src/pages/blog/index.astro -> /blog src/pages/blog/first-post.md -> /blog/first-post ``` ``` src/pages/blog/[slug].astro -> /blog/:slug src/pages/users/[id]/posts.astro -> /users/:id/posts ```
skilldb get astro-skills/Astro RoutingFull skill: 281 linesRouting — Astro
You are an expert in Astro's file-based and dynamic routing system for building websites with clean URL structures.
Overview
Astro uses file-based routing: every .astro or .md file in src/pages/ automatically becomes a route. Dynamic segments, rest parameters, and getStaticPaths give you full control over URL generation for both static and server-rendered sites.
Core Concepts
Static Routes
Files in src/pages/ map directly to URLs:
src/pages/index.astro -> /
src/pages/about.astro -> /about
src/pages/blog/index.astro -> /blog
src/pages/blog/first-post.md -> /blog/first-post
Dynamic Routes
Use bracket notation for dynamic segments:
src/pages/blog/[slug].astro -> /blog/:slug
src/pages/users/[id]/posts.astro -> /users/:id/posts
In static (pre-rendered) mode, dynamic routes require getStaticPaths to tell Astro which pages to generate:
---
// src/pages/blog/[slug].astro
import { getCollection, render } from 'astro:content';
export async function getStaticPaths() {
const posts = await getCollection('blog');
return posts.map(post => ({
params: { slug: post.id },
props: { post },
}));
}
const { post } = Astro.props;
const { Content } = await render(post);
---
<h1>{post.data.title}</h1>
<Content />
Rest Parameters
Use [...param] to match multiple path segments:
src/pages/docs/[...path].astro -> /docs, /docs/getting-started, /docs/api/reference
---
// src/pages/docs/[...path].astro
export async function getStaticPaths() {
return [
{ params: { path: undefined }, props: { title: 'Docs Home' } },
{ params: { path: 'getting-started' }, props: { title: 'Getting Started' } },
{ params: { path: 'api/reference' }, props: { title: 'API Reference' } },
];
}
const { title } = Astro.props;
---
<h1>{title}</h1>
Setting path to undefined matches the base route (/docs).
Multiple Dynamic Segments
---
// src/pages/[lang]/[category]/[slug].astro
export async function getStaticPaths() {
return [
{ params: { lang: 'en', category: 'guides', slug: 'setup' } },
{ params: { lang: 'fr', category: 'guides', slug: 'setup' } },
{ params: { lang: 'en', category: 'api', slug: 'auth' } },
];
}
const { lang, category, slug } = Astro.params;
---
<p>Language: {lang}, Category: {category}, Slug: {slug}</p>
SSR Dynamic Routes
In server-rendered mode (output: 'server'), you do not need getStaticPaths. Parameters are available directly from Astro.params:
---
// src/pages/products/[id].astro (SSR mode)
const { id } = Astro.params;
const product = await fetch(`https://api.example.com/products/${id}`).then(r => r.json());
if (!product) {
return Astro.redirect('/404');
}
---
<h1>{product.name}</h1>
<p>{product.description}</p>
Implementation Patterns
Pagination
Astro provides a built-in paginate() helper:
---
// src/pages/blog/[...page].astro
import { getCollection } from 'astro:content';
export async function getStaticPaths({ paginate }) {
const allPosts = (await getCollection('blog'))
.sort((a, b) => b.data.pubDate.valueOf() - a.data.pubDate.valueOf());
return paginate(allPosts, { pageSize: 10 });
}
const { page } = Astro.props;
// page.data - array of posts for this page
// page.currentPage - current page number (1-based)
// page.lastPage - total number of pages
// page.url.prev - URL of previous page (or undefined)
// page.url.next - URL of next page (or undefined)
---
<ul>
{page.data.map(post => (
<li><a href={`/blog/${post.id}`}>{post.data.title}</a></li>
))}
</ul>
<nav>
{page.url.prev && <a href={page.url.prev}>Previous</a>}
<span>Page {page.currentPage} of {page.lastPage}</span>
{page.url.next && <a href={page.url.next}>Next</a>}
</nav>
This generates /blog (page 1), /blog/2, /blog/3, etc.
Filtered Pagination by Tag
---
// src/pages/tags/[tag]/[...page].astro
import { getCollection } from 'astro:content';
export async function getStaticPaths({ paginate }) {
const allPosts = await getCollection('blog');
const uniqueTags = [...new Set(allPosts.flatMap(post => post.data.tags))];
return uniqueTags.flatMap(tag => {
const filtered = allPosts.filter(post => post.data.tags.includes(tag));
return paginate(filtered, {
params: { tag },
pageSize: 10,
});
});
}
const { tag } = Astro.params;
const { page } = Astro.props;
---
<h1>Posts tagged "{tag}"</h1>
<ul>
{page.data.map(post => <li>{post.data.title}</li>)}
</ul>
API Routes (Endpoints)
Create JSON or other non-HTML responses with .ts or .js files in src/pages/:
// src/pages/api/posts.json.ts
import type { APIRoute } from 'astro';
import { getCollection } from 'astro:content';
export const GET: APIRoute = async () => {
const posts = await getCollection('blog');
const data = posts.map(p => ({
id: p.id,
title: p.data.title,
pubDate: p.data.pubDate,
}));
return new Response(JSON.stringify(data), {
headers: { 'Content-Type': 'application/json' },
});
};
Redirects
Configure redirects in astro.config.mjs:
export default defineConfig({
redirects: {
'/old-blog': '/blog',
'/old-blog/[...slug]': '/blog/[...slug]',
'/twitter': {
status: 302,
destination: 'https://twitter.com/example',
},
},
});
Route Priority
Astro resolves routes in this priority order:
- Static routes (
/aboutbeats/[slug]) - Dynamic routes with more segments beat fewer (
/blog/[slug]beats/[...path]) - Named parameters beat rest parameters (
/[slug]beats/[...path]) - Pre-rendered dynamic routes beat server-rendered ones
- Endpoints beat pages
Core Philosophy
Astro's routing is built on a simple contract: the file system is the source of truth for your site's URL structure. Every file in src/pages/ becomes a route, which means you can understand the entire URL space of a site by looking at the directory tree. This eliminates routing configuration files, reduces indirection, and makes the relationship between code and URLs immediately obvious.
The getStaticPaths function represents Astro's commitment to pre-rendering as the default strategy. By requiring you to enumerate every possible parameter combination for dynamic routes in static mode, Astro forces you to think about your site as a finite set of pages that can be fully built ahead of time. This is the opposite of an SPA router that resolves paths at runtime, and it results in faster, more cacheable, and more reliable sites.
Routing in Astro also embodies a clear priority system that eliminates ambiguity. Static routes always win over dynamic ones, named parameters beat rest parameters, and pre-rendered routes outrank server-rendered ones. This deterministic resolution order means you never need to debug which route handler matched a given URL. The rules are explicit and consistent.
Anti-Patterns
-
Creating deeply nested dynamic route hierarchies. Routes like
[lang]/[category]/[subcategory]/[slug]produce complexgetStaticPathsfunctions that are hard to maintain and debug. Flatten the hierarchy where possible or use rest parameters with your own parsing logic. -
Re-fetching data inside the page component that was already available in
getStaticPaths. Thepropsreturned fromgetStaticPathsare passed directly to the page. Fetching the same data again inside the component doubles the work and slows builds. -
Using
getStaticPathsto generate hundreds of pages from a slow API. If your data source is slow, the build becomes slow. Cache API responses locally during development and use incremental builds in production when the dataset is large. -
Mixing
src/pages/blog.astroandsrc/pages/blog/index.astro. Both resolve to/blog, creating unpredictable behavior. Pick one convention and use it consistently across the project. -
Ignoring route priority rules when adding new routes. Adding a new static route that shadows an existing dynamic route can silently change which content appears at a URL. Review the priority rules before creating routes that could overlap.
Best Practices
- Use
getStaticPathsto pre-render known pages at build time, even in hybrid mode. Pre-rendered pages are faster and cheaper to serve. - Pass data through
propsingetStaticPathsrather than re-fetching inside the page component. Astro makes props available directly. - Use rest parameters (
[...slug]) for catch-all routes like documentation trees or 404 pages. - Keep route nesting shallow. Deeply nested dynamic segments make URLs harder to reason about and
getStaticPathsmore complex. - Name API endpoint files with their extension (e.g.,
posts.json.ts) so the generated file has the correct MIME type in static mode.
Common Pitfalls
- Missing
getStaticPathsin static mode: Dynamic routes withoutgetStaticPathscause a build error in static output mode. Every possible parameter combination must be enumerated. - Returning invalid params: Every
paramsvalue must be a string orundefined. Passing numbers or objects causes silent routing failures. - Rest parameter matching the root: For
[...path].astro, pass{ path: undefined }to match the base path, not an empty string. - Conflicting routes: Two files like
src/pages/blog.astroandsrc/pages/blog/index.astroresolve to the same URL and cause unpredictable behavior. Use one or the other. - Pagination off-by-one:
page.currentPageis 1-based. The first page URL omits the number (/blognot/blog/1), which can confuse link-building logic.
Install this skill directly: skilldb add astro-skills
Related Skills
Astro Basics
Astro fundamentals including project structure, components, islands architecture, and templating syntax
Astro Content Collections
Content collections in Astro for managing Markdown, MDX, JSON, and YAML content with type-safe schemas
Astro Deployment
Deploying Astro sites to Vercel, Netlify, Cloudflare Pages, and other platforms
Astro Integrations
Using React, Vue, Svelte, and other UI framework islands within Astro pages
Astro Middleware
Middleware patterns in Astro for authentication, request modification, response headers, and shared context
Astro SSR
Server-side rendering in Astro with adapters for Node, Vercel, Netlify, Cloudflare, and Deno