Skip to main content
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:
src/pages/about.astro
---
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-startedpath = "getting-started"
  • /docs/guides/installationpath = "guides/installation"
  • /docs/api/reference/configpath = "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}`
    }
  });
}
Astro supports all standard HTTP methods:
  • GET
  • POST
  • PUT
  • PATCH
  • DELETE
  • OPTIONS

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:
1

Static routes

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

Dynamic routes

Routes with parameters like /blog/[slug].astro come next.
3

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:
  • /postsposts/index.astro
  • /posts/createposts/create.astro (static wins over dynamic)
  • /posts/123posts/[id].astro
  • /posts/a/b/cposts/[...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

Return HTML content:
---
const greeting = "Hello";
---
<html>
  <body>
    <h1>{greeting}</h1>
  </body>
</html>

Practical Examples

Blog with Pagination

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

Build docs developers (and LLMs) love