Astro uses file-based routing to generate your site’s URLs. Every file in your src/pages/ directory becomes a page on your site, following the file path.
Basic Routing
Routes are automatically created based on the file structure in src/pages/:
src/pages/
├── index.astro → /
├── about.astro → /about
├── blog/
│ ├── index.astro → /blog
│ ├── first-post.astro → /blog/first-post
│ └── [slug].astro → /blog/:slug (dynamic)
└── api/
└── posts.json.ts → /api/posts.json
Astro supports .astro, .md, .mdx, .html, and .js/.ts files in the pages directory.
Static Routes
Create a new file in src/pages/ to add a static route:
---
const pageTitle = "About Us" ;
---
< html >
< head >
< title > { pageTitle } </ title >
</ head >
< body >
< h1 > { pageTitle } </ h1 >
< p > Learn more about our company. </ p >
</ body >
</ html >
This creates a page accessible at /about.
Dynamic Routes
Dynamic routes use bracket notation [param] in filenames to match any value at that position in the URL path.
Single Parameter
src/pages/blog/[slug].astro
---
import BaseLayout from '../../layouts/BaseLayout.astro' ;
export async function getStaticPaths () {
return [
{ params: { slug: 'first-post' } },
{ params: { slug: 'second-post' } },
{ params: { slug: 'third-post' } },
];
}
const { slug } = Astro . params ;
---
< BaseLayout >
< h1 > Blog Post: { slug } </ h1 >
</ BaseLayout >
This matches /blog/first-post, /blog/second-post, etc.
With Props
Pass additional data to each route using the props object:
src/pages/blog/[id].astro
---
export async function getStaticPaths () {
return [
{ params: { id: '1' }, props: { title: 'First Post' , author: 'Alice' } },
{ params: { id: '2' }, props: { title: 'Second Post' , author: 'Bob' } },
];
}
const { id } = Astro . params ;
const { title , author } = Astro . props ;
---
< article >
< h1 > { title } </ h1 >
< p > By { author } </ p >
</ article >
Multiple Parameters
Use multiple dynamic segments:
src/pages/[category]/[item].astro
---
export async function getStaticPaths () {
return [
{ params: { category: 'electronics' , item: 'laptop' } },
{ params: { category: 'electronics' , item: 'phone' } },
{ params: { category: 'books' , item: 'novel' } },
];
}
const { category , item } = Astro . params ;
---
< h1 > { category } : { item } </ h1 >
Rest Parameters
Use [...path] for catch-all routes:
src/pages/docs/[...path].astro
---
export function getStaticPaths () {
return [
{ params: { path: 'getting-started' } },
{ params: { path: 'guides/installation' } },
{ params: { path: 'api/reference/config' } },
];
}
const { path } = Astro . params ;
---
< h1 > Documentation: { path } </ h1 >
This matches:
/docs/getting-started → path = "getting-started"
/docs/guides/installation → path = "guides/installation"
/docs/api/reference/config → path = "api/reference/config"
Rest parameters can match paths at any depth, making them perfect for documentation or file browsers.
API Routes
Create API endpoints by exporting HTTP method handlers from .js or .ts files:
src/pages/api/posts.json.ts
export async function GET ({ params , request }) {
const posts = await fetchPosts ();
return new Response (
JSON . stringify ( posts ),
{
status: 200 ,
headers: {
'Content-Type' : 'application/json'
}
}
);
}
export async function POST ({ request }) {
const data = await request . json ();
// Process the data
await createPost ( data );
return new Response ( null , {
status: 201 ,
headers: {
'Location' : `/api/posts/ ${ data . id } `
}
});
}
Supported Methods
Context Object
Astro supports all standard HTTP methods:
GET
POST
PUT
PATCH
DELETE
OPTIONS
Each handler receives a context object with: {
params : Record < string , string > , // Route parameters
request : Request , // Web Request object
cookies : AstroCookies , // Cookie utilities
redirect : ( path : string ) => Response ,
url : URL , // The request URL
site : URL , // Site URL from config
generator : string , // Astro version
props : Record < string , any > , // Props from getStaticPaths
}
Route Priority
When multiple routes could match a URL, Astro uses a priority system to determine which route to use. From the source code (src/core/routing/priority.ts), routes are prioritized as follows:
Static routes
Exact matches like /about.astro have highest priority.
Dynamic routes
Routes with parameters like /blog/[slug].astro come next.
Rest parameters
Catch-all routes like /[...path].astro have lowest priority.
Given these files:
src/pages/
├── posts/index.astro # Matches /posts
├── posts/create.astro # Matches /posts/create
├── posts/[id].astro # Matches /posts/123
└── posts/[...slug].astro # Matches /posts/a/b/c
Requests resolve as:
/posts → posts/index.astro
/posts/create → posts/create.astro (static wins over dynamic)
/posts/123 → posts/[id].astro
/posts/a/b/c → posts/[...slug].astro
Route Matching
Astro converts file paths into regular expressions for matching. From src/core/routing/pattern.ts:
export function getPattern ( segments : RoutePart [][]) {
const pathname = segments
. map (( segment ) => {
return ' \\ /' + segment
. map (( part ) => {
if ( part . spread ) {
return '(.*?)' ;
} else if ( part . dynamic ) {
return '([^/]+?)' ;
} else {
return part . content
. normalize ()
. replace ( / [ .*+?^${}()|[ \]\\ ] / g , ' \\ $&' );
}
})
. join ( '' );
})
. join ( '' );
return new RegExp ( `^ ${ pathname } $` );
}
How it works : File paths are broken into segments, converted to regex patterns, then matched against incoming URLs.
Page vs. Endpoint
Pages (.astro)
Endpoints (.ts/.js)
Return HTML content: ---
const greeting = "Hello" ;
---
< html >
< body >
< h1 > { greeting } </ h1 >
</ body >
</ html >
Return data in any format: export function GET () {
return new Response (
JSON . stringify ({ message: 'Hello' }),
{ headers: { 'Content-Type' : 'application/json' } }
);
}
Practical Examples
src/pages/blog/[page].astro
---
export async function getStaticPaths ({ paginate }) {
const posts = await fetchAllPosts ();
return paginate ( posts , { pageSize: 10 });
}
const { page } = Astro . props ;
---
< div >
{ page . data . map ( post => (
< article >
< h2 > { post . title } </ h2 >
< p > { post . excerpt } </ p >
</ article >
)) }
</ div >
< nav >
{ page . url . prev && < a href = { page . url . prev } > Previous </ a > }
{ page . url . next && < a href = { page . url . next } > Next </ a > }
</ nav >
Dynamic API with Database
src/pages/api/users/[id].ts
import { db } from '../../../lib/db' ;
export async function GET ({ params }) {
const user = await db . users . findById ( params . id );
if ( ! user ) {
return new Response ( null , { status: 404 });
}
return new Response ( JSON . stringify ( user ), {
headers: { 'Content-Type' : 'application/json' }
});
}
export async function PUT ({ params , request }) {
const updates = await request . json ();
const user = await db . users . update ( params . id , updates );
return new Response ( JSON . stringify ( user ), {
headers: { 'Content-Type' : 'application/json' }
});
}
Best Practices
Use Static Routes When Possible They’re faster and easier to understand.
Organize with Folders Group related pages together for better maintainability.
Type Your API Routes Use TypeScript for better type safety in endpoints.
Handle Errors Always return appropriate status codes in API routes.
Learn More
Layouts Reuse common page structures
Content Collections Type-safe content with built-in routing