Topic 8 of 39
Catch-All Routes
Overview
Sometimes you need to match an unknown number of URL segments, like a deeply nested documentation site. Catch-all routes are created by adding an ellipsis inside brackets [...folderName]. Optional catch-all routes use double brackets [[...folderName]].
Syntax
tsx
// app/docs/[...slug]/page.tsx
// Matches: /docs/a, /docs/a/b, /docs/a/b/c
// params.slug will be an array: ['a', 'b', 'c']
export default function DocsPage({ params }: { params: { slug: string[] } }) {
return <h1>Docs path: {params.slug.join('/')}</h1>;
}Common Pitfalls
- Using
[...slug]and expecting it to match the root/docs. It won't. You must use optional catch-all[[...slug]]to match the root route as well. - Not accounting for
params.slugbeing undefined when using optional catch-all routes at the root level.
Interview Questions
Q:
What is the difference between
[...slug] and [[...slug]]?A:
[...slug] requires at least one URL segment after the parent path to match. [[...slug]] is an optional catch-all, meaning it will also match the parent path exactly (e.g., /docs in addition to /docs/a).
Real-World Example
A flexible product filter route that handles multiple nested categories.
example
tsx
// app/shop/[...categories]/page.tsx
// URL: /shop/mens/shoes/sneakers
export default function ShopFilter({ params }: { params: { categories: string[] } }) {
// categories = ["mens", "shoes", "sneakers"]
return (
<div>
<h1>Filtering by: {params.categories.join(' > ')}</h1>
</div>
);
}Check Your Knowledge
Test your understanding of Catch-All Routes with these quick questions.