Skip to main content

Overview

Astro uses file-based routing, where files in src/pages/ automatically become routes on your site. This guide covers common routing patterns and advanced techniques.

File-Based Routing

The structure of your src/pages/ directory determines your site’s URL structure.

Basic Routes

src/pages/
├── index.astro          → /
├── about.astro          → /about
├── contact.astro        → /contact
└── blog/
    ├── index.astro      → /blog
    └── first-post.astro → /blog/first-post

Nested Routes

Create nested directories for organized URL structures:
src/pages/
└── products/
    ├── index.astro           → /products
    ├── categories/
    │   └── [category].astro  → /products/categories/electronics
    └── [id].astro            → /products/123

Dynamic Routes

Use square brackets [param] to create dynamic route parameters.

Single Parameter

src/pages/products/[id].astro
---
import { getProduct } from '../../api';

const { id } = Astro.params;
const product = await getProduct(Number(id));
---

<html>
  <head>
    <title>{product.name} | Online Store</title>
  </head>
  <body>
    <h1>{product.name}</h1>
    <p>${product.price}</p>
    <img src={product.image} alt={product.name} />
  </body>
</html>

Static Path Generation

For static sites, generate all possible paths at build time:
src/pages/products/[id].astro
---
export async function getStaticPaths() {
  const products = [
    { id: 1, name: 'Laptop', price: 999 },
    { id: 2, name: 'Mouse', price: 29 },
    { id: 3, name: 'Keyboard', price: 79 },
  ];

  return products.map((product) => ({
    params: { id: String(product.id) },
    props: { product },
  }));
}

const { product } = Astro.props;
---

<h1>{product.name}</h1>
<p>${product.price}</p>

Multiple Parameters

src/pages/blog/[year]/[month]/[slug].astro
---
export async function getStaticPaths() {
  const posts = [
    { year: '2024', month: '01', slug: 'getting-started', title: 'Getting Started' },
    { year: '2024', month: '02', slug: 'advanced-tips', title: 'Advanced Tips' },
  ];

  return posts.map((post) => ({
    params: { 
      year: post.year, 
      month: post.month, 
      slug: post.slug 
    },
    props: { post },
  }));
}

const { year, month, slug } = Astro.params;
const { post } = Astro.props;
---

<article>
  <h1>{post.title}</h1>
  <p>Published: {month}/{year}</p>
</article>

Catch-All Routes

Use [...path] to match any number of path segments.

Basic Catch-All

src/pages/[...path].astro
---
const { path } = Astro.params;
// path could be: "docs", "docs/guide", "docs/guide/routing", etc.

const segments = path ? path.split('/') : [];
---

<h1>Path: /{path}</h1>
<ul>
  {segments.map((segment) => (
    <li>{segment}</li>
  ))}
</ul>

Documentation Site Pattern

src/pages/docs/[...slug].astro
---
import { getCollection } from 'astro:content';

export async function getStaticPaths() {
  const docs = await getCollection('docs');
  
  return docs.map((doc) => ({
    params: { slug: doc.id },
    props: { doc },
  }));
}

const { doc } = Astro.props;
const { Content } = await doc.render();
---

<article>
  <h1>{doc.data.title}</h1>
  <Content />
</article>

Named Catch-All with Fallback

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;

if (!post) {
  return Astro.redirect('/404');
}

const { Content } = await render(post);
---

<article>
  <h1>{post.data.title}</h1>
  <Content />
</article>

Redirects

Handle redirects for moved content, aliases, and legacy URLs.

Static Redirects

Define redirects in your Astro config:
astro.config.mjs
import { defineConfig } from 'astro/config';

export default defineConfig({
  redirects: {
    '/old-page': '/new-page',
    '/blog/[...slug]': '/articles/[...slug]',
    '/temporary': {
      status: 302,
      destination: '/new-location',
    },
  },
});

Dynamic Redirects

Handle redirects programmatically:
src/pages/legacy/[id].astro
---
const { id } = Astro.params;

// Redirect to new URL structure
return Astro.redirect(`/products/${id}`, 301);
---

Conditional Redirects

src/pages/dashboard.astro
---
const session = Astro.cookies.get('session');

if (!session) {
  return Astro.redirect('/login');
}
---

<h1>Dashboard</h1>
<p>Welcome back!</p>

API Routes

Create API endpoints that return JSON or other data formats.

Basic API Route

src/pages/api/products.ts
import type { APIRoute } from 'astro';

export const GET: APIRoute = async ({ request }) => {
  const products = [
    { id: 1, name: 'Laptop', price: 999 },
    { id: 2, name: 'Mouse', price: 29 },
  ];

  return new Response(JSON.stringify(products), {
    status: 200,
    headers: {
      'Content-Type': 'application/json',
    },
  });
};

Dynamic API Routes

src/pages/api/products/[id].ts
import type { APIRoute } from 'astro';

export const GET: APIRoute = async ({ params }) => {
  const { id } = params;
  
  const product = await fetchProduct(id);
  
  if (!product) {
    return new Response(JSON.stringify({ error: 'Not found' }), {
      status: 404,
      headers: { 'Content-Type': 'application/json' },
    });
  }

  return new Response(JSON.stringify(product), {
    status: 200,
    headers: { 'Content-Type': 'application/json' },
  });
};

function fetchProduct(id: string) {
  // Fetch from database
  return { id, name: 'Product', price: 99 };
}

POST Requests

src/pages/api/contact.ts
import type { APIRoute } from 'astro';

export const POST: APIRoute = async ({ request }) => {
  const data = await request.json();
  
  // Validate data
  if (!data.email || !data.message) {
    return new Response(JSON.stringify({ 
      error: 'Email and message are required' 
    }), {
      status: 400,
      headers: { 'Content-Type': 'application/json' },
    });
  }

  // Process contact form
  await sendEmail(data);

  return new Response(JSON.stringify({ 
    success: true,
    message: 'Message sent successfully'
  }), {
    status: 200,
    headers: { 'Content-Type': 'application/json' },
  });
};

async function sendEmail(data: any) {
  // Email sending logic
}

Priority and Route Matching

Astro resolves routes with this priority order:
1

Static routes

Exact file matches like /about.astro have highest priority.
2

Dynamic routes

Single parameter routes like /[id].astro are checked next.
3

Catch-all routes

Catch-all routes like /[...path].astro have lowest priority.

Example Routing Order

src/pages/
├── blog.astro              → /blog (1st priority)
├── [page].astro            → /about, /contact (2nd priority)
└── [...slug].astro         → /anything/else (3rd priority)

Advanced Patterns

Structure routes for multiple languages:
src/pages/
├── en/
│   ├── index.astro       → /en
│   └── about.astro       → /en/about
└── es/
    ├── index.astro       → /es
    └── about.astro       → /es/about
Or use dynamic routing:
src/pages/[lang]/[...slug].astro
---
const { lang, slug } = Astro.params;
const validLangs = ['en', 'es', 'fr'];

if (!validLangs.includes(lang)) {
  return Astro.redirect('/en');
}
---
Create a custom 404 page:
src/pages/404.astro
---
const currentPath = Astro.url.pathname;
---

<html>
  <head>
    <title>404 - Page Not Found</title>
  </head>
  <body>
    <h1>404 - Page Not Found</h1>
    <p>The page <code>{currentPath}</code> doesn't exist.</p>
    <a href="/">Go home</a>
  </body>
</html>

Best Practices

Organize by Feature

Group related pages in directories for better maintainability.

Use Named Parameters

Choose descriptive parameter names like [slug] or [id] instead of generic names.

Handle Edge Cases

Always validate parameters and handle missing data gracefully.

Leverage TypeScript

Use TypeScript for type-safe routing and API routes.

Troubleshooting

1

Route conflicts

If routes conflict, check the priority order. More specific routes should be in separate files.
2

Dynamic routes not generating

Ensure getStaticPaths() is exported and returns the correct format.
3

Redirects not working

Verify your adapter supports redirects. Some static hosts require additional configuration.

Build docs developers (and LLMs) love