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.

BUILDING SOMETHING SIMILAR?

If you'd rather have this built and shipped than build it yourself, I'm available for freelance and contract work.