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
Optional Catch-All
Matches zero or more segments
Documentation Sites
Perfect for nested docs
File Browsers
Navigate folder structures
🔹 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