Catch-All Routes in Next.js

Handling multiple URL segments with flexible routing

🎯 What are Catch-All Routes?

Catch-all routes capture multiple URL segments into an array. Use three dots inside square brackets [...slug] to match any number of path segments for flexible routing patterns.


// app/docs/[...slug]/page.js
export default function Docs({ params }) {
  return <p>Path: {params.slug.join('/')}</p>
}
                                    

Catch-All Route Types

📚

Required Catch-All

Matches one or more segments

[...slug] Requires params
🔓

Optional Catch-All

Matches zero or more segments

[[...slug]] Optional params
🗂️

Documentation Sites

Perfect for nested docs

Multi-level Flexible depth
🌲

File Browsers

Navigate folder structures

Nested paths Dynamic depth

🔹 Required Catch-All Routes

Required catch-all routes match one or more URL segments. The segments are captured as an array in the params object, requiring at least one segment.

🔸 Folder Structure

app/
└── docs/
    └── [...slug]/
        └── page.js        → /docs/getting-started ✅
                          → /docs/api/reference ✅
                          → /docs/guides/auth/setup ✅
                          → /docs ❌ (no segments)

🔸 Basic Implementation

// app/docs/[...slug]/page.js
export default function Docs({ params }) {
  return (
    <div>
      <h1>Documentation</h1>
      <p>Current path: {params.slug.join('/')}</p>
      <ul>
        {params.slug.map((segment, index) => (
          <li key={index}>Segment {index + 1}: {segment}</li>
        ))}
      </ul>
    </div>
  )
}

// URL: /docs/getting-started/installation
// params.slug = ["getting-started", "installation"]

🔹 Optional Catch-All Routes

Optional catch-all routes match zero or more segments. Use double square brackets to make the route work even without any segments in the URL.

🔸 Folder Structure

app/
└── shop/
    └── [[...categories]]/
        └── page.js        → /shop ✅
                          → /shop/electronics ✅
                          → /shop/electronics/laptops ✅
                          → /shop/electronics/laptops/gaming ✅

🔸 Handling Optional Segments

// app/shop/[[...categories]]/page.js
export default function Shop({ params }) {
  const categories = params.categories || []
  
  return (
    <div>
      <h1>Shop</h1>
      {categories.length === 0 ? (
        <p>Browse all products</p>
      ) : (
        <p>Category: {categories.join(' → ')}</p>
      )}
    </div>
  )
}

// URL: /shop
// params.categories = undefined

// URL: /shop/electronics/laptops
// params.categories = ["electronics", "laptops"]

🔹 Building a Documentation Site

Catch-all routes are perfect for documentation sites with nested pages. Build a flexible docs structure that handles any depth of nesting automatically.

🔸 Documentation Page

// app/docs/[...slug]/page.js
async function getDocContent(slug) {
  const path = slug.join('/')
  const res = await fetch(`https://api.example.com/docs/${path}`)
  return res.json()
}

export default async function DocPage({ params }) {
  const doc = await getDocContent(params.slug)
  
  return (
    <article>
      <nav>
        {params.slug.map((segment, i) => (
          <span key={i}>
            {i > 0 && ' / '}
            {segment}
          </span>
        ))}
      </nav>
      <h1>{doc.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: doc.content }} />
    </article>
  )
}

🔸 Breadcrumb Navigation

// components/Breadcrumbs.js
import Link from 'next/link'

export default function Breadcrumbs({ segments }) {
  return (
    <nav>
      <Link href="/docs">Docs</Link>
      {segments.map((segment, index) => {
        const href = `/docs/${segments.slice(0, index + 1).join('/')}`
        return (
          <span key={index}>
            {' / '}
            <Link href={href}>{segment}</Link>
          </span>
        )
      })}
    </nav>
  )
}

🔹 File Browser Example

Create a file browser interface using catch-all routes to navigate through nested folder structures with unlimited depth.

🔸 File Browser Component

// app/files/[[...path]]/page.js
async function getFiles(path) {
  const pathStr = path ? path.join('/') : ''
  const res = await fetch(`https://api.example.com/files/${pathStr}`)
  return res.json()
}

export default async function FileBrowser({ params }) {
  const path = params.path || []
  const files = await getFiles(path)
  
  return (
    <div>
      <h1>Files: /{path.join('/')}</h1>
      <ul>
        {files.map(file => (
          <li key={file.name}>
            {file.type === 'folder' ? (
              <Link href={`/files/${[...path, file.name].join('/')}`}>
                📁 {file.name}
              </Link>
            ) : (
              <span>📄 {file.name}</span>
            )}
          </li>
        ))}
      </ul>
    </div>
  )
}

🔹 Generating Static Paths

Pre-render catch-all routes at build time by generating all possible path combinations. This improves performance for static documentation sites.

🔸 Static Generation

// app/docs/[...slug]/page.js
export async function generateStaticParams() {
  const docs = await fetch('https://api.example.com/docs/all')
    .then(res => res.json())
  
  return docs.map((doc) => ({
    slug: doc.path.split('/'),
  }))
}

export default async function DocPage({ params }) {
  const doc = await getDoc(params.slug)
  return <article>{doc.content}</article>
}

// Generates:
// /docs/getting-started → slug: ["getting-started"]
// /docs/api/reference → slug: ["api", "reference"]
// /docs/guides/auth/setup → slug: ["guides", "auth", "setup"]

🔹 Handling Edge Cases

Handle special cases like empty paths, invalid segments, and missing content gracefully for a better user experience.

🔸 Validation and Error Handling

import { notFound } from 'next/navigation'

export default async function CatchAllPage({ params }) {
  const slug = params.slug || []
  
  // Validate segments
  const isValid = slug.every(segment => 
    /^[a-z0-9-]+$/.test(segment)
  )
  
  if (!isValid) {
    notFound()
  }
  
  // Fetch content
  const content = await getContent(slug)
  
  if (!content) {
    notFound()
  }
  
  return <div>{content}</div>
}

🔹 Best Practices

Follow these guidelines when working with catch-all routes:

  • Use descriptive names: [...slug], [...path], [...categories]
  • Validate segments: Check for valid characters and patterns
  • Handle empty arrays: Use optional catch-all when appropriate
  • Add breadcrumbs: Help users navigate nested structures
  • Implement search: Make deep content discoverable
  • Generate static paths: Pre-render known routes for performance
  • Use proper 404s: Handle missing content gracefully

🧠 Test Your Knowledge

What syntax creates an optional catch-all route?