Skip to content

Dynamic Pages from Data

Next.js App Router uses generateStaticParams() to enumerate dynamic routes at build time. Next Pages Router uses getStaticPaths(). Astro uses getStaticPaths() in a [slug].astro file — same name as Pages Router, but you return an array of objects with a params key (optionally a props key too) and access them via Astro.props and Astro.params in the frontmatter.

Needs a full Astro project to run. Use npm create astro@latest to scaffold one.

App Router generateStaticParams vs Astro getStaticPaths

Section titled “App Router generateStaticParams vs Astro getStaticPaths”
React
// app/blog/[slug]/page.tsx (Next.js App Router)
import { allPosts } from 'contentlayer/generated';
export async function generateStaticParams() {
return allPosts.map(post => ({ slug: post.slug }));
}
export default function PostPage({ params }) {
const post = allPosts.find(p => p.slug === params.slug);
return (
<article>
<h1>{post.title}</h1>
<p>{post.description}</p>
</article>
);
}
Astro
---
// src/pages/blog/[slug].astro (Astro)
import { getCollection, render } from 'astro:content';
export async function getStaticPaths() {
const posts = await getCollection('blog');
return posts.map(post => ({
params: { slug: post.slug },
props: { post },
}));
}
const { post } = Astro.props;
const { Content } = await render(post);
---
<article>
<h1>{post.data.title}</h1>
<Content />
</article>

In Astro, getStaticPaths and the page template live in the same file. The props key lets you pass the full collection entry directly — no second lookup needed.

React
// pages/blog/[slug].tsx (Next.js Pages Router)
export async function getStaticPaths() {
const posts = await fetchAllPosts(); // or contentlayer
return {
paths: posts.map(p => ({ params: { slug: p.slug } })),
fallback: false,
};
}
export async function getStaticProps({ params }) {
const post = await fetchPost(params.slug);
return { props: { post } };
}
export default function PostPage({ post }) {
return (
<article>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.contentHtml }} />
</article>
);
}
Astro
---
// src/pages/blog/[slug].astro (Astro)
import { getCollection, render } from 'astro:content';
export async function getStaticPaths() {
const posts = await getCollection('blog');
// params defines the URL; props pass data to the page
return posts.map(post => ({
params: { slug: post.slug },
props: { post },
}));
}
// Both params and props land here — no second getStaticProps needed
const { post } = Astro.props;
const { Content } = await render(post);
---
<article>
<h1>{post.data.title}</h1>
<Content />
</article>

Next Pages Router splits path enumeration (getStaticPaths) from data fetching (getStaticProps). Astro merges them: pass props inside getStaticPaths and skip the second round-trip.

src/pages/blog/[slug].astro
---
import { getCollection, render } from 'astro:content';
import BaseLayout from '../../layouts/BaseLayout.astro';
export async function getStaticPaths() {
const posts = await getCollection('blog', ({ data }) => !data.draft);
return posts.map((post) => ({
params: { slug: post.slug },
props: { post },
}));
}
const { post } = Astro.props;
const { Content, headings } = await render(post);
const { title, date, tags = [] } = post.data;
---
<BaseLayout title={title}>
<article>
<header>
<h1>{title}</h1>
<time datetime={date.toISOString()}>
{date.toLocaleDateString('en-US', { dateStyle: 'long' })}
</time>
{tags.length > 0 && (
<ul class="tags">
{tags.map((tag) => <li>{tag}</li>)}
</ul>
)}
</header>
<Content />
</article>
</BaseLayout>
Where does `getStaticPaths()` live in an Astro project?
What does each object in the `getStaticPaths` return array represent?
What is the Astro equivalent of Next App Router's `generateStaticParams`?