- Next.js
- Advanced Routing & Interception
- Dynamic Route Catch-Alls: [...slug] vs [[...slug]]
Dynamic Route Catch-Alls: [...slug] vs [[...slug]]
Catch-all routes [...slug] match one or more subsequent path segments and supply them as a string array. Optional catch-all routes [[...slug]] match zero or more segments, meaning they also match the parent route itself without requiring a separate root page.tsx file.
Learning Objectives
Compare matching patterns between [slug], [...slug], and [[...slug]].
Build documentation and CMS page routers using optional catch-all segments.
Type dynamic parameters correctly with Promise<{ slug?: string[] }>.
Prevent route ambiguity and segment conflict errors during build.
Prerequisites & Background Knowledge
Why Interviewers Care
Standard knowledge check for configuring headless CMS platforms, documentation portals (like Nextra/Docs), and multi-level e-commerce taxonomies.
The Challenge
Contrast single catch-all routes [...slug] with optional catch-all routes [[...slug]]. Write an optional catch-all documentation handler that supports both the root /docs landing page and deep /docs/a/b/c pages in a single file.
Code Architecture & Implementation
1// app/docs/[[...slug]]/page.tsx2import { notFound } from 'next/navigation';34interface DocsPageProps {5 // In Next.js 15+, params is a Promise!6 // Optional catch-all means slug can be undefined when at /docs7 params: Promise<{8 slug?: string[];9 }>;10}1112export default async function DocsPage({ params }: DocsPageProps) {13 const { slug } = await params;1415 // Case 1: Route is /docs (slug is undefined)16 if (!slug || slug.length === 0) {17 return (18 <main className="p-8">19 <h1 className="text-3xl font-bold">Documentation Home</h1>20 <p>Welcome to our developer docs. Select a topic from the sidebar.</p>21 </main>22 );23 }2425 // Case 2: Deep nested route: /docs/components/buttons/primary26 const fullPath = slug.join('/');27 const doc = await fetchDocContent(fullPath);2829 if (!doc) {30 notFound();31 }3233 return (34 <article className="p-8 prose">35 <div className="text-sm text-gray-500 mb-2">Path: /docs/{fullPath}</div>36 <h1>{doc.title}</h1>37 <div dangerouslySetInnerHTML={{ __html: doc.content }} />38 </article>39 );40}
Architectural Explanation
[...slug] requires at least one parameter segment. If the user navigates to /docs, Next.js will not match [...slug]. Double brackets [[...slug]] make the parameter optional. When visited at /docs, params.slug is undefined. When visited at /docs/api/v1/auth, params.slug is ["api", "v1", "auth"]. This enables unified CMS and documentation architectures where all hierarchy is driven dynamically by backend page trees.
Common Mistakes & Anti-Patterns
Expecting `params.slug` to be a string instead of a string array `string[]`.
Creating both `app/docs/page.tsx` AND `app/docs/[[...slug]]/page.tsx`, causing a conflicting route definition build error.
Not guarding against `undefined` when reading optional catch-all slug arrays.
Interview Follow-Up Traps
The interviewer asks: "What happens if I have both app/docs/[id]/page.tsx and app/docs/[...slug]/page.tsx?" Next.js specificity rules apply: single dynamic segments `[id]` have higher specificity and match `/docs/123`, while `[...slug]` matches multi-segment paths like `/docs/123/edit`.
Frequently Asked Questions
How do you define generateStaticParams for catch-all routes?
Return arrays of strings: `return [{ slug: [] }, { slug: ['guide'] }, { slug: ['guide', 'installation'] }]`.
Does [[...slug]] match search params?
No. Dynamic parameters match URL path segments only; query strings are parsed via `searchParams`.
