Catch-All & Optional Catch-All Routes
[slug] matches exactly one path segment. Sometimes you need a route that matches any number of segments — arbitrary depth, decided by the data rather than the folder structure. That's what a catch-all route is for.Catch-all segments: [...slug]
Folder structure
app/
docs/
[...slug]/
page.tsxapp/docs/[...slug]/page.tsx
type Props = {
params: { slug: string[] }
}
export default function DocsPage({ params }: Props) {
// /docs/a -> slug = ["a"]
// /docs/a/b -> slug = ["a", "b"]
// /docs/a/b/c -> slug = ["a", "b", "c"]
const path = params.slug.join('/')
return <h1>Docs: {path}</h1>
}/docs itself (with nothing after it) is not matched by [...slug] and results in a 404 unless a separate app/docs/page.tsx exists to handle the bare route.Optional catch-all segments: [[...slug]]
/docs and /docs/anything/nested/here.Folder structure
app/
docs/
[[...slug]]/
page.tsxapp/docs/[[...slug]]/page.tsx
type Props = {
params: { slug?: string[] }
}
export default function DocsPage({ params }: Props) {
// /docs -> slug = undefined
// /docs/a -> slug = ["a"]
// /docs/a/b -> slug = ["a", "b"]
const path = params.slug?.join('/') ?? '(index)'
return <h1>Docs: {path}</h1>
}params.slug can be undefined for the base route, its TypeScript type should mark it optional (slug?: string[]) and your code should handle the missing case explicitly.[...slug] | [[...slug]] | |
|---|---|---|
Matches one or more segments | Yes | Yes |
Matches zero segments (the base route) | No — 404 unless a separate page.tsx exists | Yes |
|
|
|
Use cases
Catch-all routes shine whenever the shape of your URL tree is decided by external data rather than by your file system:
A CMS-driven page tree, where an editor can create pages at any depth (
/about,/about/team,/about/team/leadership) without you adding a matching folder for each one.A documentation site with nested, arbitrary-depth pages, where a single
[...slug]route reads the full path and looks up the matching content on the server.A single optional catch-all handling both a section's landing page and every nested page beneath it, avoiding a separate
page.tsxjust for the index route.