Routing

Astro uses file-based routing under src/pages/. Every .astro, .md, .mdx, .ts, or .js file in that directory becomes a route.

Static routes

src/pages/index.astro       -> /
src/pages/about.astro        -> /about
src/pages/blog/index.astro   -> /blog
src/pages/blog/first.md      -> /blog/first

Dynamic routes

Use [param] brackets for dynamic segments:

src/pages/blog/[slug].astro  -> /blog/:slug
src/pages/[...path].astro    -> catch-all (404, custom routing)

Static mode (default)

Dynamic routes require getStaticPaths() to tell Astro which pages to build:

---
// src/pages/blog/[slug].astro
export async function getStaticPaths() {
  const posts = await fetch('https://api.example.com/posts').then(r => r.json());
  return posts.map(post => ({
    params: { slug: post.slug },
    props: { post },
  }));
}

const { post } = Astro.props;
---
<h1>{post.title}</h1>
<p>{post.body}</p>

This is equivalent to Next.js generateStaticParams().

On-demand mode

For server-rendered routes, skip getStaticPaths() and read params directly:

---
// src/pages/blog/[slug].astro
export const prerender = false;  // opt out of static

const { slug } = Astro.params;
const post = await fetch(`https://api.example.com/posts/${slug}`).then(r => r.json());

if (!post) {
  return Astro.redirect('/404');
}
---
<h1>{post.title}</h1>

Rest parameters (catch-all)

---
// src/pages/[...path].astro
const { path } = Astro.params;
// path = "a/b/c" for /a/b/c
---

Endpoints

.ts or .js files in src/pages/ become API endpoints:

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

export const GET: APIRoute = async () => {
  const data = await fetch('https://api.example.com/posts').then(r => r.json());
  return new Response(JSON.stringify(data), {
    headers: { 'Content-Type': 'application/json' },
  });
};

Endpoints support GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS, and ALL.

For static builds, endpoints run at build time and produce static files. For on-demand routes, they run per request.

Redirects

In config

// astro.config.mjs
export default defineConfig({
  redirects: {
    '/old': '/new',
    '/blog/[...slug]': '/articles/[...slug]',
  },
});

Programmatic

---
return Astro.redirect('/destination', 301);
---

Middleware

// src/middleware.ts
import { defineMiddleware } from 'astro:middleware';

export const onRequest = defineMiddleware((context, next) => {
  if (context.url.pathname === '/daily/today') {
    const today = new Date().toISOString().split('T')[0];
    return context.redirect(`/daily/${today}`);
  }
  return next();
});

404 pages

Create src/pages/404.astro for a custom 404 page. In static mode, it generates a 404.html file. In on-demand mode, it renders for unmatched routes.

Trailing slashes

Configure in astro.config.mjs:

export default defineConfig({
  trailingSlash: 'always',  // or 'never' or 'ignore'
});

Comparison with Next.js routing

FeatureNext.js (App Router)Astro
File locationapp/src/pages/
Dynamic params[slug]/page.tsx[slug].astro
Catch-all[...slug]/page.tsx[...slug].astro
Static generationgenerateStaticParams()getStaticPaths()
Layoutslayout.tsx (nested)Manual via layout components
API routesroute.ts.ts files exporting HTTP methods
Middlewaremiddleware.ts in project rootsrc/middleware.ts
Redirectsnext.config.js redirectsastro.config.mjs redirects or Astro.redirect()
404not-found.tsx or notFound()404.astro