Skip to content
C.W.K.
Stream
Lesson 05 of 10 · published

Dynamic Routes

~22 min · dynamic segment, catch-all, optional

Level 0Curious
0 XP0/68 lessons0/11 achievements
0/120 XP to next level120 XP to go0% complete

Three flavors of dynamic segment

Folder patternExample URLparams shape
[slug]/blog/hello{ slug: 'hello' }
[...slug]/docs/a/b/c{ slug: ['a','b','c'] }
[[...slug]]/docs or /docs/a/b{ slug: undefined } or { slug: ['a','b'] }

Multiple segments combine cleanly

You can have several dynamic segments in one path. params is the shape of all of them merged into a single object.

404 vs render

If a request hits /blog/foo and no generateStaticParams pre-built it, the default behavior is to render on demand. Set export const dynamicParams = false if you want unknown slugs to 404 instead.

Separate the set of routes worth prebuilding from the set of routes the product permits. generateStaticParams answers the first question. dynamicParams = false answers the second. Popular articles can be prebuilt while a long tail still renders on demand; a finite documentation version list may need strict 404s.

A catch-all route is not a shortcut for every hierarchy. When each segment has a distinct meaning, an array of strings hides the information architecture and weakens types. Use catch-all only when depth is genuinely open-ended, as with file paths or nested documentation. Request a single segment, several segments, and the empty optional catch-all. Compare a known prebuilt path with an unknown path before and after disabling dynamic params. Build output and HTTP status should both match the policy you wrote down.

Code

Optional catch-all·tsx
// app/docs/[[...slug]]/page.tsx
type Props = { params: Promise<{ slug?: string[] }> };

export default async function DocsPage({ params }: Props) {
  const { slug } = await params;
  if (!slug) return <DocsIndex />;

  const path = slug.join('/'); // 'getting-started/install'
  const doc = await getDoc(path);
  if (!doc) notFound();
  return <DocContent doc={doc} />;
}
Two dynamic segments·tsx
// app/[locale]/blog/[slug]/page.tsx
import { notFound } from 'next/navigation';

type Props = { params: Promise<{ locale: string; slug: string }> };

export default async function Page({ params }: Props) {
  const { locale, slug } = await params;
  const post = await getPost(locale, slug);
  if (!post) notFound();
  return <Article post={post} />;
}
Force 404 on unknown slugs·tsx
// app/blog/[slug]/page.tsx
export const dynamicParams = false;

export async function generateStaticParams() {
  const slugs = await getAllPostSlugs();
  return slugs.map(slug => ({ slug }));
}

External links

Exercise

Build a docs route with app/docs/[[...slug]]/page.tsx that serves both /docs (index) and /docs/a/b/c (a nested doc). Add generateStaticParams for at least three known slugs and confirm dynamicParams = false 404s an unknown one.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.