[...slug] Captures Multiple Segments
Catch-All Routes
A folder named [...slug] matches one or more path segments as an array. [[...slug]] makes the match optional.
What you'll learn
- Use `[...slug]` for nested paths
- Use `[[...slug]]` for an optional catch-all
- Read `params.slug` as a string array
Sometimes one route needs to match an unknown number of path segments — docs pages, a file browser, or a CMS-driven site. Catch-all routes are how Next.js handles that.
Required Catch-All
A folder named [...slug] captures one or more segments after that point.
app/docs/[...slug]/page.tsx
# matches /docs/a, /docs/a/b, /docs/getting-started/install params.slug is a string array of the captured segments.
// app/docs/[...slug]/page.tsx
export default async function DocsPage({
params,
}: {
params: Promise<{ slug: string[] }>;
}) {
const { slug } = await params;
// /docs/a/b → slug = ['a', 'b']
return (
<article>
<h1>{slug.join(' / ')}</h1>
</article>
);
} Note: /docs itself does not match. A required catch-all needs at least one segment.
Optional Catch-All
Wrap the brackets twice — [[...slug]] — and the base path matches too.
app/shop/[[...slug]]/page.tsx
# matches /shop, /shop/shoes, /shop/shoes/red // app/shop/[[...slug]]/page.tsx
export default async function ShopPage({
params,
}: {
params: Promise<{ slug?: string[] }>;
}) {
const { slug } = await params;
if (!slug) return <h1>All products</h1>;
return <h1>Category: {slug.join(' / ')}</h1>;
} The slug array is undefined at the base URL.
When to Use Each
- Use a regular dynamic route (
[slug]) when you know exactly one variable segment. - Use
[...slug]when the depth is unknown but at least one segment is required. - Use
[[...slug]]when even the base URL should hit the same handler.
Docs sites and file browsers are the classic catch-all use cases. The next lesson introduces route groups for organization without affecting the URL.
Route Groups — (auth) (marketing) →