Chapter 03 — Routing Deep Dive: Dynamic, Nested & Catch-All Routes

Chapter 03 — Routing Deep Dive: Dynamic, Nested & Catch-All Routes

Hey everyone! Now that you know a folder = a route, let's go deeper. Real apps need more than static paths like /about — they need /blog/my-post, /shop/electronics/laptops, and URLs whose structure you don't know in advance. Next.js handles all of it, still with plain folder names.

What we will cover:

  • Dynamic segments — [param]
  • Reading route params in a page
  • Catch-all and optional catch-all routes
  • Nested routes and how layouts stack
  • Link vs <a> — why it matters
  • Programmatic navigation with useRouter

1. Dynamic Segments — [param]

app/
└── blog/
    └── [slug]/
        └── page.js

Visiting:
  /blog/hello-world   → slug = "hello-world"
  /blog/react-tips    → slug = "react-tips"

One file. Infinite URLs.
// app/blog/[slug]/page.js
export default async function BlogPost({ params }) {
    const { slug } = await params;   // Next.js 15: params is a Promise
    return <h1>Post: {slug}</h1>;
}

Note: starting Next.js 15, params (and searchParams) are async — you must await them. This trips up a lot of people copying older tutorials.

2. Multiple Dynamic Segments

app/
└── shop/
    └── [category]/
        └── [product]/
            └── page.js

/shop/electronics/laptop-15   → category = "electronics", product = "laptop-15"

export default async function ProductPage({ params }) {
    const { category, product } = await params;
    ...
}

3. Catch-All Routes — [...param]

app/
└── docs/
    └── [...slug]/
        └── page.js

/docs/a           → slug = ["a"]
/docs/a/b         → slug = ["a", "b"]
/docs/a/b/c       → slug = ["a", "b", "c"]

One file, matches ANY depth after /docs/.
Great for documentation sites with nested pages.

4. Optional Catch-All — [[...param]]

app/
└── shop/
    └── [[...filters]]/
        └── page.js

/shop             → filters = undefined  ← the DOUBLE brackets make /shop itself match too
/shop/shoes       → filters = ["shoes"]
/shop/shoes/nike  → filters = ["shoes", "nike"]

Difference from [...param]: without the extra brackets, "/shop"
alone (with NO segments after it) would 404. Double brackets fix that.

5. Nested Routes & How Layouts Stack

app/
├── layout.js              ← wraps EVERYTHING (root layout, required)
├── dashboard/
│   ├── layout.js           ← wraps everything under /dashboard
│   ├── page.js             ← "/dashboard"
│   └── settings/
│       └── page.js         ← "/dashboard/settings"

Visiting /dashboard/settings renders:

  RootLayout(
    DashboardLayout(
      SettingsPage()
    )
  )

Every layout above a route wraps it. This is how a shared
sidebar/navbar for a whole section works — one layout.js,
automatically applied to every page beneath it.

6. Link vs Plain <a>

❌ <a href="/about">About</a>
   → full page reload, browser re-downloads everything, SLOW

✅ import Link from "next/link";
   <Link href="/about">About</Link>
   → client-side navigation, no full reload
   → Next.js automatically PREFETCHES the linked page in the
     background when it scrolls into view, so the click feels instant

Always use Link for internal navigation. It's not a style preference — plain <a> throws away the SPA-like fast navigation Next.js gives you for free.

7. Programmatic Navigation — useRouter

"use client";
import { useRouter } from "next/navigation";

function LoginButton() {
    const router = useRouter();

    async function handleLogin() {
        await loginUser();
        router.push("/dashboard");   // navigate after an action
        // router.replace("/dashboard")  → like push, but no back-button history
        // router.refresh()              → re-fetch server data for current route
    }

    return <button onClick={handleLogin}>Log In</button>;
}

⚠️ Note the import: next/navigation, not the old next/router (that's the Pages Router version and won't work here).

Key Points to Remember

  • [param] = one dynamic segment, [...param] = catch-all, [[...param]] = optional catch-all
  • In Next.js 15+, params and searchParams are async — always await them
  • Layouts nest and stack — every layout above a route automatically wraps it
  • Always use <Link> for internal links — it enables client-side nav + prefetching
  • Use useRouter from next/navigation for navigation triggered by code (after form submit, etc.)

What's Next?

Next chapter, we tackle the single biggest mental shift coming from plain React: Server Components vs Client Components — what runs where, and why it matters.

Keep coding, keep learning! See you in the next one!