Guides
How to Create Dynamic Routes in Next.js
Dynamic routes let a single template render many pages based on a URL parameter — a blog post slug, a product ID. This covers the folder conventions and how to pre-render them at build time.
Prerequisites
- arrow_rightA Next.js App Router project
- arrow_rightA data source with the list of items to render (array, database, or CMS)
Step 1: Create a Dynamic Segment
A folder named [slug] inside app/blog/ creates a route matching any value at that position — app/blog/[slug]/page.tsx handles /blog/anything.
app/blog/[slug]/page.tsx
export default async function BlogPost({ params }: { params: { slug: string } }) {
const post = await getPost(params.slug);
if (!post) return <NotFound />;
return <article><h1>{post.title}</h1></article>;
}Step 2: Pre-Render Known Paths With generateStaticParams
export async function generateStaticParams() {
const posts = await getAllPosts();
return posts.map((post) => ({ slug: post.slug }));
}This tells Next.js exactly which slug values to pre-render as static HTML at build time, rather than rendering every visit on demand.
Step 3: Catch-All Segments for Nested Paths
A folder named [...slug] matches any number of path segments (e.g. /docs/a/b/c), useful for documentation trees or deeply nested content structures where the depth isn't fixed.
Common Errors
- arrow_rightPage shows a 404 for a valid slug — check that generateStaticParams returns exactly the same shape of value used in the URL (string, not number)
- arrow_rightBuild takes a very long time — for very large datasets, only pre-render the most important paths and let the rest generate on-demand with ISR
- arrow_rightTypeScript complaining about the params type — in recent Next.js versions params is a Promise and needs to be awaited
Frequently Asked Questions
Do I have to use generateStaticParams?add
No — without it, dynamic routes still work but render on each request (or on first request with caching, depending on configuration) rather than being pre-built as static HTML.
What's the difference between [slug] and [...slug]?add
[slug] matches exactly one path segment; [...slug] (catch-all) matches one or more segments, useful for arbitrarily nested paths like documentation trees.
How do I handle a slug that doesn't exist?add
Check for the record inside the page component and call notFound() from next/navigation, which renders the route's not-found.tsx file with a proper 404 status.