Mastering Catch-All & Optional Catch-All Routes in Next.js ([...slug] & [[...slug]])

Himmat Regar Jul 14, 2025, 11:30 PM
nextjs
Views 1020
Blog Thumbnail

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

[...slug].js vs [[...slug]].js — the 2025 Guide

Dynamic routing is one of the super-powers that makes Next.js feel effortless. But what happens when you can’t predict how many path segments a URL will have—or whether it will have any at all? Enter catch-all and optional catch-all routes.


1 | What exactly is a “catch-all” route?

A catch-all segment matches “this segment plus anything that follows.”
You declare it by adding an ellipsis inside the brackets:

pages/shop/[...slug].js          # Pages Router
app/shop/[...slug]/page.js       # App Router (v13+)

 

So /shop/clothes, /shop/clothes/tops/t-shirts, or any deeper path will all resolve to the same page. The param arrives as an array:

// Pages Router example
export default function Page({ params }) {
  const { slug } = params;          // ['clothes', 'tops', 't-shirts']
}

 

Official docs example ➜ Next.js


2 | And what’s an optional catch-all?

Sometimes you want the base route to resolve as well (/shop and deeper paths). Wrap the segment in double brackets:

 
pages/shop/[[...slug]].js         # Matches /shop  βœ…
app/shop/[[...slug]]/page.js      # App Router equivalent

 

If no extra path is present, slug is undefined, otherwise it’s an array.
Docs illustration ➜ Next.jsNext.js


3 | Pages Router vs App Router syntax

Router Regular catch-all Optional catch-all
Pages (legacy) pages/docs/[...slug].js pages/docs/[[...slug]].js
App (Next 13-15) app/docs/[...slug]/page.js app/docs/[[...slug]]/page.js

Both routers pass the captured parts as arrays (string[])—the only difference is the file-system location and the data-fetching helpers you use.


4 | Data fetching for deep URLs

Router Build-time SSG helper Runtime SSR helper
Pages getStaticPaths + getStaticProps getServerSideProps
App generateStaticParams dynamic = 'force-dynamic' or plain async code

Static generation tips

  1. Return one object per full path in getStaticPaths/generateStaticParams.

  2. For huge site-maps, set fallback: 'blocking' (Pages) or dynamicParams: true (App) so uncommon paths are rendered on demand and then cached.

  3. Don’t forget to revalidate when content can change.


5 | Linking & Navigation

import Link from 'next/link';

<Link href={`/shop/${['clothes', 'tops'].join('/')}`}>
  Browse Tops
</Link>

 

Next.js pre-fetches the bundle automatically, so even the deepest path feels instant.


6 | SEO & usability checklist

  • Canonical URLs — compute them at render time so search engines understand each unique address.

  • Breadcrumbs — split the slug array to build hierarchical navigation.

  • Sitemaps — iterate over your CMS data or route manifest to produce XML; split large sitemaps into files of ≤ 50 k URLs.

  • 404 clarity — fall back to a custom 404 page if the slug array doesn’t map to real content.


7 | Common pitfalls

Symptom Cause Quick Fix
Build fails: route conflicts You have both index.js and [[...slug]].js in the same folder Keep only one default entry point (optional catch-all can replace index).
router.query.slug is empty on first render Static page is hydrated client-side Guard for undefined before accessing.
Params array ordering is wrong Manual .split('/') on URL Use framework-provided params/useParams instead.
Optional segment ignored in App Router Folder name typo—must be [[...slug]] not (slug) Rename the folder correctly. Next.js

8 | Real-world use-cases

  1. Docs sites/docs/intro, /docs/guides/deployment/aws.

  2. CMS pages — marketers add nested slugs without touching code.

  3. Multi-level categories/shop/men/shirts/flannel.

  4. User-generated paths/u/jane/recipes/cakes/chocolate.

  5. Funnel builders / A/B tests — optional segment shows default landing page when slug missing.


9 | Conclusion

Catch-all ([...slug]) and optional catch-all ([[...slug]]) routes turn a single component into a swiss-army knife that gracefully handles any depth of URL. Combine them with static generation helpers for speed, add smart guards for undefined params, and you’ll have a routing setup that scales from ten pages to a million—without ever touching a server config file.

Happy routing! πŸš€

FAQS

# Question Answer
1 What is a catch-all route? A catch-all segment ([...slug]) matches one or more path segments that follow its folder, so pages like /docs/a, /docs/a/b/c, etc. are all handled by the same file. The captured parts arrive as an array (e.g. ['a', 'b', 'c']).
2 How is an optional catch-all different? [[...slug]] behaves like [...slug] and also matches the base path with zero extra segments (e.g. /docs). If no segment exists, the param is undefined.
3 What file or folder names do I use? Pages Router: pages/docs/[...slug].js (catch-all) or pages/docs/[[...slug]].js (optional). App Router: app/docs/[...slug]/page.js or app/docs/[[...slug]]/page.js.
4 How do I pre-render these routes at build time? Pages: export getStaticPaths to list full paths and set fallback (false, true, or 'blocking'). App: export generateStaticParams; adjust dynamicParams or revalidate for runtime behavior.
5 What does the slug param look like at runtime? /shop/clothes/topsslug = ['clothes','tops']. For [[...slug]], visiting /shop yields slug = undefined.
6 Can I combine other dynamic segments with a catch-all? Yes. Example: app/users/[userId]/posts/[...slug]/page.tsx matches /users/42/posts/2025/july/highlights.
7 Any SEO concerns? Not if you pre-render when possible, set canonical URLs dynamically, and ensure unique meta tags for each resolved path.
8 How do I link to deep paths? Use <Link> with the joined array: <Link href={/shop/${slug.join('/')}}>…</Link>. Next.js prefetches the bundle automatically.
9 Why do I get route conflicts at the project root? A root-level [...slug] can shadow other routes (e.g. /users/[id]). Place the catch-all inside its own folder or restructure your routing.
10 How do I show a default view when no slug is provided? Use an optional catch-all ([[...slug]]) and branch in your component: if !slug, render the landing page; otherwise render the deep page.
11 Why is router.query.slug empty on first render? In statically generated Pages routes, the first client render hydrates an already-rendered HTML page, so query.slug can be undefined initially—guard for it before using.
12 Can I build breadcrumbs from the slug array? Absolutely. Split or reduce the slug array to construct hierarchical links for breadcrumb navigation.

 

Comments

Please login to leave a comment.

No comments yet.

Related Posts

deploy-nextjs-to-vercel
1059 viewsnextjs
Himmat Regar β€’ Jul 14, 2025, 6:01 PM

Zero-to-Prod: Deploying Your Next.js Project on Vercel ...

nextjs-api-routes-backend-functionality
1641 viewsnextjs
Himmat Regar β€’ Jun 29, 2025, 5:03 PM

How to Use API Routes in Next.js for Backend Functional...

nextjs-incremental-static-regeneration-isr-guide
1490 viewsnextjs
Himmat Regar β€’ Jun 29, 2025, 5:18 PM

Incremental Static Regeneration (ISR) Explained with Ex...

multi-language-website-nextjs-i18n
1329 viewsnextjs
Himmat Regar β€’ Jun 30, 2025, 5:14 PM

Building a Multi-Language Website with Next.js 15 & Mod...

nextjs-tailwind-css-perfect-ui-pairing
1367 viewsnextjs
Himmat Regar β€’ Jun 30, 2025, 5:25 PM

Next.js 15 + Tailwind CSS 4: The Perfect UI Pairing

why-every-developer-should-learn-typescript-in-2025
1245 viewsnextjs
Himmat Regar β€’ Jul 3, 2025, 5:56 PM

Why Every Developer Should Learn TypeScript in 2025

mastering-dynamic-routes-nextjs
1009 viewsnextjs
Himmat Regar β€’ Jul 14, 2025, 5:24 PM

Mastering Dynamic Routes in Next.js: [id].js & App Rout...

image-optimization-nextjs-everything-you-should-know
1522 viewsnextjs
Himmat Regar β€’ Jun 29, 2025, 5:20 PM

Image Optimization in Next.js: Everything You Should Kn...

nextjs-vs-react-differences
906 viewsnextjs
Himmat Regar β€’ Jun 27, 2025, 11:09 AM

Next.js vs React: What’s the Difference and When to Use...

nextjs-markdown-blog-tutorial
939 viewsnextjs
Himmat Regar β€’ Jun 27, 2025, 10:18 AM

How to Build Your First Blog Using Next.js and Markdown