Catch-All Routes

Match multiple URL segments with [...name] and optional [[...name]] - params as arrays.

Overview

Catch-all routes match multiple URL segments in one route using [...name]. The param becomes an array. The optional version [[...name]] also matches the parent path. Each creates a URL.

app
docs
[...slug]
page.tsx
/docs/*
PatternExample URLCreates URL?
[...slug]/docs/api/referenceYes - catch-all creates URL
[[...slug]]/shop and /shop/clothingYes - optional catch-all creates URL
useParams() is auto-imported - no import needed to access catch-all params as an array.

What are Catch-All Routes?

Catch-all routes match any number of segments after the parent path using [...name]. The param is an array of matched segments. Lower priority than static and single dynamic routes.

  • Matches multiple segments: any number after the parent path
  • Array parameter: param becomes an array of segments
  • Lower priority: static and [slug] are matched first
  • Optional version: [[...name]] for optional catch-all
app
docs
[...slug]
page.tsx
/docs/*

/docs/getting-started matches with slug = ['getting-started'], /docs/guides/routing/basics with slug = ['guides', 'routing', 'basics'].

Basic Usage

Create a catch-all by naming a folder or file [...name]. Requires at least one segment. Creates a URL for any depth.

app
blog
[...slug]
page.tsx
products
[...path]
page.tsx
/blog/*
/products/*
[...slug] requires at least one segment. Use [[...slug]] for an optional catch-all that also matches the parent.

Accessing Parameters

Use useParams() (auto-imported). The param is an array.

app/docs/[...slug]/page.tsx
export default function DocsPage() {
  const { slug } = useParams()
  // slug is array of URL segments

  return (
    <div>
      <h1>Documentation</h1>
      <p>Path: {slug?.join(' / ')}</p>
      <p>Depth: {slug?.length || 0}</p>
    </div>
  )
}
URLslug valueCreates URL?
/docs/getting-started['getting-started']Yes
/docs/api/reference['api', 'reference']Yes
/docs/guides/routing/basics['guides', 'routing', 'basics']Yes
/docs/advanced/custom/hooks['advanced', 'custom', 'hooks']Yes
app/blog/[...slug]/page.tsx
export default function BlogArchive() {
  const { slug } = useParams()
  const [posts, setPosts] = useState([])

  useEffect(() => {
    const path = slug?.join('/')
    fetchPosts(path).then(setPosts)
  }, [slug])

  return (
    <div>
      <h1>Archive: {slug?.join(' / ') || 'Home'}</h1>
      {posts.map(post => (
        <div key={post.id}>{post.title}</div>
      ))}
    </div>
  )
}

Nested Catch-All Routes

Combine catch-all with static and dynamic segments. Each combination creates a URL.

app
products
[category]
[...slug]
page.tsx
blog
featured
page.tsx
[...slug]
page.tsx
/products/:cat/*
/blog/featured
/blog/*
app/products/[category]/[...slug]/page.tsx
export default function ProductPage() {
  const { category, slug } = useParams()

  return (
    <div>
      <h1>Category: {category}</h1>
      <p>Path: {slug?.join(' / ')}</p>
      <p>Segments: {slug?.length || 0}</p>
    </div>
  )
}

Optional Catch-All Routes

[[...name]] makes the catch-all optional - it matches the parent and nested paths. Creates a URL for both.

app
shop
[[...slug]]
page.tsx
docs
[[...slug]]
page.tsx
/shop, /shop/*
/docs, /docs/*
app/shop/[[...slug]]/page.tsx
export default function ShopPage() {
  const { slug } = useParams()

  if (!slug || slug.length === 0) {
    return <h1>Shop Home</h1>
  }

  return (
    <div>
      <h1>Category: {slug.join(' / ')}</h1>
      <p>Depth: {slug.length}</p>
    </div>
  )
}
URLslug valueCreates URL?
/shopundefinedYes - parent creates URL
/shop/clothing['clothing']Yes
/shop/clothing/shirts['clothing', 'shirts']Yes
/docsundefinedYes - parent creates URL
/docs/getting-started['getting-started']Yes
Optional catch-all is ideal for docs where /docs shows a landing page and /docs/getting-started shows content.

File-Based Catch-All Routes

Catch-all as flat files without folders. Creates a URL directly and reduces nesting.

app
docs
[...slug].tsx
shop
[[...slug]].tsx
blog
[...slug].tsx
/docs/*
/shop, /shop/*
/blog/*
app/blog/[...slug].tsx
export default function BlogArchive() {
  const { slug } = useParams()
  return <h1>Archive: {slug?.join(' / ')}</h1>
}

Route Priority

Catch-all has lower priority than static and single dynamic routes. All create URLs, but static wins.

  1. Static routes - /blog/featured - creates URL, highest priority
  2. Dynamic single - [slug] → /blog/:slug - creates URL
  3. Catch-all - [...slug] → /blog/* - creates URL
  4. Optional catch-all - [[...slug]] → /docs/* - creates URL, lowest
app
blog
featured
page.tsx
[slug]
page.tsx
[...slug]
page.tsx
/blog/featured
/blog/:slug
/blog/*
URLMatched RouteCreates URL?
/blog/featuredfeatured/page.tsx - staticYes
/blog/hello-world[slug]/page.tsx - dynamicYes
/blog/2024/01/hello-world[...slug]/page.tsx - catch-allYes

Use Cases

Catch-all routes are ideal for multi-segment structures. All create URLs.

Documentation
Multi-level docs with variable depth
/docs/guides/routing/basics
E-commerce Categories
Nested categories
/products/electronics/phones/iphone
Blog Archives
Date-based archives
/blog/2024/01/hello-world
Multi-language Sites
Language prefixes with variable paths
/en/docs/getting-started
Use CaseExample URLCreates URL?
CMS Content/wiki/guides/routingYes
API Versioning/api/v1/users/123Yes
File Browser/files/docs/guidesYes
Wiki Pages/wiki/guides/routing/basicsYes

Complete Example

Catch-all only - [...param] and [[...param]] patterns that create URLs.

app
blog
featured
page.tsx
[slug]
page.tsx
[...slug]
page.tsx
docs
[[...slug]]
layout.tsx
page.tsx
products
[category]
[...slug]
page.tsx
shop
[[...slug]]
page.tsx
api
v1
[...path].tsx
/blog/featured
/blog/:slug
/blog/*
/docs, /docs/*
/products/:cat/*
/shop, /shop/*
/api/v1/*
PatternExample URLCreates URL?
/blog/featured/blog/featuredYes - static creates URL
/blog/:slug/blog/hello-worldYes - dynamic creates URL
/blog/*/blog/2024/01/hello-worldYes - catch-all creates URL
/docs/* (optional)/docsYes - optional catch-all creates URL
/products/:category/*/products/electronics/phones/iphoneYes - nested catch-all creates URL
/shop/* (optional)/shop/clothing/shirtsYes - optional catch-all creates URL
/api/v1/*/api/v1/users/123Yes - flat file catch-all creates URL
Was this helpful?