Catch-all Segments
Use [...slug] to match any number of path segments in a single route.
Syntax
app/docs/[...slug]/page.tsxA catch-all segment, written [...slug], matches one or more segments and collects them into an array. It is useful for docs sites or any deeply nested, data-driven structure.
Variants
[...slug]— matches/docs/a,/docs/a/b, etc. (one or more).[[...slug]]— optional catch-all, also matches the base/docs.
Example
// app/docs/[...slug]/page.tsx
// /docs/a/b/c -> slug = ['a', 'b', 'c']
export default async function Docs({
params,
}: {
params: Promise<{ slug: string[] }>;
}) {
const { slug } = await params;
return <p>Path: {slug.join(' / ')}</p>;
}When to use it
- A documentation site uses [...path] to handle arbitrarily deep URLs like /docs/api/v2/endpoints.
- A CMS-driven site maps any URL to a page-builder template using a catch-all route at the root.
- An optional catch-all [[...slug]] makes the index page and all its children share a single route file.
More examples
Catch-all folder structure
The [...slug] folder matches one or more path segments and collects them into an array.
app/
└── docs/
└── [...slug]/
└── page.tsx # matches /docs, /docs/a, /docs/a/b ...Reading catch-all params
The slug param is a string array, so joining it reconstructs the full path for data fetching.
// app/docs/[...slug]/page.tsx
type Props = { params: Promise<{ slug: string[] }> };
export default async function DocsPage({ params }: Props) {
const { slug } = await params;
const path = slug.join('/');
return <h1>Docs path: {path}</h1>;
}Optional catch-all segment
Double brackets make the segment optional, so the same file handles both / and /any/deep/path.
// app/[[...slug]]/page.tsx
type Props = { params: Promise<{ slug?: string[] }> };
export default async function Page({ params }: Props) {
const { slug } = await params;
if (!slug) return <h1>Home</h1>;
return <h1>Path: {slug.join('/')}</h1>;
}
Discussion