Catch-All and Optional Catch-All Routes in Next.js

A catch-all route matches many URL segments with one folder. Learn the difference between catch-all and optional catch-all syntax and when each fits.

6 min read

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.tsxApp.tsx
// 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.tsxApp.tsx
// 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 patternMatches the base pathMatches multiple segments
Normal dynamic segmentNo, one segment onlyNo
Catch-all segmentNoYes
Optional catch-all segmentYesYes

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

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.
RunePowered by Rune AI

Frequently Asked Questions

How many URL segments can a catch-all route match?

Any number greater than zero. A single folder named with the catch-all syntax matches one segment, ten segments, or any depth after it, and every segment lands in one array.

Does params.slug come back as a string or an array for a catch-all route?

It comes back as an array of strings, one entry per matched URL segment, even when the URL only has one segment after the folder.

When would I use an optional catch-all instead of a normal catch-all?

Use an optional catch-all when the base path itself, with nothing after it, should also render through the same page, such as a documentation home page and its nested subpages sharing one file.

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.