Skip to content

Dynamic Routes

In Next.js App Router you use [slug]/page.tsx + generateStaticParams() to pre-render dynamic pages at build time. Astro uses the same bracket-filename convention — [slug].astro — paired with a required getStaticPaths() export inside the frontmatter.

Dynamic routes require multiple generated pages — run these examples in a full Astro project (npm create astro@latest) locally.

Next.js
// app/blog/[slug]/page.tsx (Next.js App Router)
export async function generateStaticParams() {
const posts = await getPosts();
return posts.map((post) => ({ slug: post.slug }));
}
export default function BlogPost({
params,
}: {
params: { slug: string };
}) {
return <h1>Post: {params.slug}</h1>;
}
Astro
---
// src/pages/blog/[slug].astro
export async function getStaticPaths() {
const posts = await getPosts();
return posts.map((post) => ({
params: { slug: post.slug },
props: { post }, // pass data alongside params
}));
}
const { slug } = Astro.params;
const { post } = Astro.props;
---
<h1>{post.title}</h1>

Key differences to note:

  1. getStaticPaths() is exported from inside the frontmatter — not as a separate file export like Next.js.
  2. Each path object can carry a props field to colocate fetched data with the path — no second fetch needed on the page.
  3. Astro.params holds the dynamic segment value, equivalent to params.slug in Next.js.
Next.js Pages Router
// Next.js Pages Router equivalent
// pages/blog/[slug].tsx
import type { GetStaticPaths, GetStaticProps } from 'next';
export const getStaticPaths: GetStaticPaths = async () => {
const posts = await getPosts();
return {
paths: posts.map((p) => ({ params: { slug: p.slug } })),
fallback: false,
};
};
export const getStaticProps: GetStaticProps = async ({ params }) => {
const post = await getPost(params!.slug as string);
return { props: { post } };
};
export default function BlogPost({ post }: { post: Post }) {
return <article><h1>{post.title}</h1></article>;
}
Astro
---
// src/pages/blog/[slug].astro
// getStaticPaths + data fetching in ONE function:
export async function getStaticPaths() {
const posts = await getPosts();
return posts.map((post) => ({
params: { slug: post.slug },
props: { post },
}));
}
// props arrive via Astro.props (same as any component)
const { post } = Astro.props;
---
<article>
<h1>{post.title}</h1>
<div set:html={post.body} />
</article>
flowchart TD
  pages["src/pages/"] --> blog["blog/"]
  blog --> index["index.astro"]
  blog --> slug["[slug].astro"]
  index --> indexURL["/blog (list all posts)"]
  slug --> slugURL["/blog/:slug (individual post)"]
Blog file tree mapping files to routes

Astro supports rest-parameter catch-all routes with [...path].astro, which maps to [[...slug]]/page.tsx in Next.js.

Next.js
// app/docs/[[...slug]]/page.tsx
export default function DocsPage({
params,
}: {
params: { slug?: string[] };
}) {
const path = params.slug?.join('/') ?? '';
return <div>Docs path: {path}</div>;
}
Astro
---
// src/pages/docs/[...path].astro
const { path } = Astro.params;
// path is a string like "getting-started/intro"
---
<div>Docs path: {path}</div>
Which function must you export from a dynamic .astro page to tell Astro which paths to pre-render?
Where is `getStaticPaths()` declared in an Astro dynamic route file?
How do you access the dynamic segment value (e.g. the slug) inside an Astro page?
What is the Astro equivalent of Next.js `[[...slug]]` catch-all segments?