Catch-all Segments

[...slug]

Three dots catch one or more segments as an array. app/docs/[...slug]/page.tsx:

URLparams
/docs/shipping{ slug: ['shipping'] }
/docs/returns/refunds/upi{ slug: ['returns', 'refunds', 'upi'] }
/docs404 (needs at least one segment)
export default async function DocsPage({ params }: { params: Promise<{ slug: string[] }> }) {
  const { slug } = await params;
  return <h1>Help: {slug.join(' / ')}</h1>;
}

How Matching Works

Example

A small route matcher that supports static parts, [id] and [...slug]. Next.js does the same, with static routes winning over dynamic ones.

type Params = Record<string, string | string[]>;

function match(pattern: string, path: string): Params | null {
  const want = pattern.split('/').filter(Boolean);
  const got = path.split('/').filter(Boolean);
  const params: Params = {};
  for (let i = 0; i < want.length; i++) {
    const seg = want[i];
    if (seg.startsWith('[...')) {
      const rest = got.slice(i);
      if (rest.length === 0) return null;
      params[seg.slice(4, -1)] = rest;
      return params;
    }
    if (got[i] === undefined) return null;
    if (seg.startsWith('[')) params[seg.slice(1, -1)] = got[i];
    else if (seg !== got[i]) return null;
  }
  return want.length === got.length ? params : null;
}

const routes = ['/products/new', '/products/[id]', '/docs/[...slug]'];
for (const url of ['/products/new', '/products/42', '/docs/returns/upi', '/docs', '/cart']) {
  const hit = routes.map((r) => [r, match(r, url)] as const).find(([, p]) => p !== null);
  console.log(url.padEnd(20), hit ? hit[0] + ' ' + JSON.stringify(hit[1]) : '404');
}

Output

/products/new        /products/new {}
/products/42         /products/[id] {"id":"42"}
/docs/returns/upi    /docs/[...slug] {"slug":["returns","upi"]}
/docs                404
/cart                404