Catch-all Segments
Match multiple path segments at once using [...slug] and optional catch-all [[...slug]] routes.
Catch-all Segments
Adding three dots inside brackets, like [...slug], creates a catch-all segment that matches any number of path parts and collects them into an array.
app/docs/[...slug]/page.tsx
/docs/a → params.slug === ["a"]/docs/a/b → params.slug === ["a", "b"]/docs/a/b/c → params.slug === ["a", "b", "c"]/docs → does NOT match — needs at least one segmentOptional Catch-all Segments
Wrapping the folder in double brackets, [[...slug]], makes the segment optional — it also matches the parent path with zero extra segments.
app/docs/[[...slug]]/page.tsx
/docs → params.slug === undefined/docs/a → params.slug === ["a"]/docs/a/b → params.slug === ["a", "b"]Real-World Use Case
Catch-all segments are the natural fit for documentation sites and CMS-driven pages, where content lives at arbitrarily nested paths (/docs/guides/routing/dynamic) that you don't want to hand-create a folder for.
// app/docs/[[...slug]]/page.tsxexport default async function DocsPage({ params }) { const segments = params.slug ?? []; // [] for /docs itself const path = segments.join('/'); const doc = await getDocByPath(path || 'index');
return <article dangerouslySetInnerHTML={{ __html: doc.html }} />;}FAQs
No — a catch-all segment must be the last part of the path in that folder chain; nothing can follow it.
Use a catch-all when the depth of the path is unknown or CMS-driven; use individual [segments] when the structure is fixed and predictable.
Summary
Catch-all segments handle arbitrarily deep paths, and the optional variant also covers the base path itself. Next, you'll look at parallel routes, which render more than one page in the same layout simultaneously.