A Next.js catch-all route matches an unlimited number of URL segments with a single folder. Add three dots inside the brackets, and Next.js captures everything after that point in the path as an array, instead of a single string.
// app/shop/[...slug]/page.tsx
export default async function Page({
params,
}: {
params: Promise<{ slug: string[] }>;
}) {
const { slug } = await params;
return <p>Path: {slug.join(" / ")}</p>;
}Visiting /shop/clothing renders "Path: clothing". Visiting /shop/clothing/tops/t-shirts renders "Path: clothing / tops / t-shirts". The same file handles both requests, because every segment that follows the shop folder collects into one array.
Catch-all does not match the base path
A folder written with the catch-all syntax requires at least one segment after it. Visiting the base shop path with nothing following it does not match this route at all, and Next.js falls through to a 404 page unless a separate file handles that exact path. This is the detail that trips people up: a catch-all folder is not automatically a fallback for its own parent path, it only extends past it.
Optional catch-all also matches the base path
Wrapping the same pattern in a second set of brackets makes the trailing segments optional instead of required. The same file now also matches the bare base path, with the params value resolving to undefined instead of an array.
// app/docs/[[...slug]]/page.tsx
type Params = Promise<{ slug?: string[] }>;
export default async function Page({ params }: { params: Params }) {
const { slug } = await params;
const path = slug ? slug.join("/") : "home";
return <p>Docs page: {path}</p>;
}Visiting /docs renders "Docs page: home". Visiting /docs/routing/dynamic-routes renders "Docs page: routing/dynamic-routes". One file now serves the documentation home page and every nested page beneath it.
Choosing between them
| Route pattern | Matches the base path | Matches multiple segments |
|---|---|---|
| Normal dynamic segment | No, one segment only | No |
| Catch-all segment | No | Yes |
| Optional catch-all segment | Yes | Yes |
Use a catch-all when the base path should be handled by a different file, such as a real shop index page with its own layout and content. Use an optional catch-all when one component should own the base path and every nested path beneath it, which is common for documentation sites and content trees pulled from a CMS, where the number of nested sections is not known in advance.
Common mistake
Forgetting that slug is always an array, even for a single segment, causes bugs. Code that tries to render slug directly as text, instead of joining or indexing the array first, prints something like a comma-separated object reference instead of a clean path. Handle it as a list from the start, and check for undefined when the segment is optional.
For the single-segment version of this pattern, see dynamic routes in Next.js. For reading the resolved values alongside the query string, see params vs searchParams.
Rune AI
Key Insights
- A catch-all folder is written with three dots inside brackets, like [...slug].
- It matches one or more URL segments and returns them as an array.
- An optional catch-all adds a second set of brackets and also matches the base path.
- A normal catch-all folder does not match the base path with nothing after it.
- Both variants still receive their values through the params prop.
Frequently Asked Questions
How many URL segments can a catch-all route match?
Does params.slug come back as a string or an array for a catch-all route?
When would I use an optional catch-all instead of a normal catch-all?
Conclusion
A catch-all route absorbs any number of URL segments into a single array on params, and the optional variant additionally matches the base path with nothing after it. Reach for either one when a single template needs to handle a variable-depth URL.
More in this topic
`generateMetadata` Explained with Real Examples
What generateMetadata does, when it runs, and how to use it for real routes: awaited params, deduplicated data fetching, extending parent metadata, and returning a 404 from metadata.
Canonical URLs in Next.js: `metadataBase`, `alternates.canonical`, and Dynamic Pages
How canonical URLs work in the Next.js App Router: setting metadataBase once, writing alternates.canonical per route, handling dynamic segments, and what happens when the base URL is missing.
Open Graph and Twitter Card Metadata in Next.js
How to write Open Graph and Twitter card metadata in the Next.js App Router: the openGraph and twitter fields, automatic card defaults, article tags, and image merge rules.