Next.js Curriculum
EasyAdvanced Routing100% Free20 min read

Dynamic Route Catch-Alls: [...slug] vs [[...slug]]

The 30-Second Interview Answer

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
Next.js App Router dynamic routesURL path segment parsing

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.tsx
2import { notFound } from 'next/navigation';
3
4interface DocsPageProps {
5 // In Next.js 15+, params is a Promise!
6 // Optional catch-all means slug can be undefined when at /docs
7 params: Promise<{
8 slug?: string[];
9 }>;
10}
11
12export default async function DocsPage({ params }: DocsPageProps) {
13 const { slug } = await params;
14
15 // 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 }
24
25 // Case 2: Deep nested route: /docs/components/buttons/primary
26 const fullPath = slug.join('/');
27 const doc = await fetchDocContent(fullPath);
28
29 if (!doc) {
30 notFound();
31 }
32
33 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

Can you have multiple catch-all segments in a single route?

No. You can only have one catch-all segment, and it must be at the end of the path.

Return arrays of strings: `return [{ slug: [] }, { slug: ['guide'] }, { slug: ['guide', 'installation'] }]`.

No. Dynamic parameters match URL path segments only; query strings are parsed via `searchParams`.