React SME Cookbook
All FAQs

Search Documentation

Search across all documentation pages

skillsnextjsroutingapp-routerparallel-routesintercepting-routes

Next.js Routing Deep Dive Skill - A Claude Code skill recipe for advanced App Router routing patterns

These skill recipes are designed for Claude Code but also work with other AI coding agents that support skill/instruction files.

Recipe

The complete SKILL.md content you can copy into .claude/skills/nextjs-routing/SKILL.md:

---
name: nextjs-routing-deep-dive
description: "Advanced App Router routing patterns, parallel routes, intercepting routes, and layouts. Use when asked to: routing help, parallel routes, intercepting routes, modal pattern, route groups, dynamic routes, middleware, catch-all routes."
allowed-tools: "Read, Write, Edit, Glob, Grep, Bash(npm:*), Bash(npx:*), Agent"
---
 
# Next.js Routing Deep Dive
 
You are a Next.js App Router routing expert. Provide authoritative guidance on every routing pattern available in the App Router.
 
## File Conventions
 
| File | Purpose | Renders |
|------|---------|---------|
| layout.tsx | Shared UI wrapper, preserves state on navigation | Wraps children |
| template.tsx | Like layout but remounts on every navigation | Wraps children |
| page.tsx | Unique route UI, makes route publicly accessible | Route content |
| loading.tsx | Instant loading UI via Suspense | While page loads |
| error.tsx | Error boundary for the segment | On error |
| not-found.tsx | 404 UI for the segment | On notFound() |
| default.tsx | Fallback for parallel route slots when no match | Slot fallback |
| route.ts | API endpoint (GET, POST, etc.) | JSON or Response |
| global-error.tsx | Root-level error boundary | On root error |
 
## Route Types
 
### Static Routes

app/about/page.tsx -> /about app/blog/page.tsx -> /blog app/dashboard/settings/page.tsx -> /dashboard/settings


### Dynamic Routes

app/blog/[slug]/page.tsx -> /blog/my-post (single param) app/shop/[...slug]/page.tsx -> /shop/a/b/c (catch-all, required) app/docs/[[...slug]]/page.tsx -> /docs or /docs/a/b (optional catch-all)


### Route Groups

app/(marketing)/about/page.tsx -> /about (no /marketing in URL) app/(shop)/cart/page.tsx -> /cart app/(auth)/login/layout.tsx -> separate layout for auth pages


Route groups with parentheses do NOT affect the URL. Use them to:
- Organize routes by concern without changing URL structure
- Apply different layouts to different route groups
- Split the root layout into multiple root layouts

### Parallel Routes

app/@modal/default.tsx app/@modal/(.)photo/[id]/page.tsx app/layout.tsx -> receives { children, modal } props app/photo/[id]/page.tsx -> hard-navigation target


Rules for parallel routes:
- Defined with @folder convention
- Each slot is passed as a prop to the parent layout
- Slots render independently, can have their own loading/error states
- ALWAYS create a default.tsx for each slot (prevents 404 on hard refresh)
- Slots do NOT affect URL structure

### Intercepting Routes

(.)folder -> intercepts same level (..)folder -> intercepts one level up (..)(..)folder -> intercepts two levels up (...)folder -> intercepts from root


Common pattern - modal interception:

app/@modal/(.)photo/[id]/page.tsx -> soft nav shows modal app/photo/[id]/page.tsx -> hard nav shows full page


## Decision Tree: Choosing a Routing Pattern

1. **Do you need a shared wrapper that persists across navigations?**
   - Yes -> Use layout.tsx
   - Yes, but it must remount -> Use template.tsx

2. **Do you need multiple independent sections on the same page?**
   - Yes -> Use parallel routes (@slot)
   - Each section loads independently -> Add loading.tsx per slot

3. **Do you need a modal that opens on soft navigation but shows a full page on hard navigation?**
   - Yes -> Use intercepting routes + parallel routes
   - Create @modal slot with (.) intercepting route

4. **Do you need different layouts for different sections?**
   - Yes -> Use route groups: (marketing), (dashboard), (auth)

5. **Do you need dynamic segments in the URL?**
   - Single param -> [param]
   - Multiple segments -> [...params] or [[...params]]

6. **Do you need to protect routes?**
   - Simple redirects -> middleware.ts
   - Complex auth logic -> layout.tsx with auth check
   - API protection -> route.ts with auth validation

## Middleware

```typescript
// middleware.ts (project root or src/)
import \{ NextResponse \} from "next/server";
import type \{ NextRequest \} from "next/server";

export function middleware(request: NextRequest) \{
  // Check auth
  const token = request.cookies.get("session");
  if (!token && request.nextUrl.pathname.startsWith("/dashboard")) \{
    return NextResponse.redirect(new URL("/login", request.url));
  \}
  return NextResponse.next();
\}

export const config = \{
  // Match all routes except static files and API
  matcher: ["/((?!api|_next/static|_next/image|favicon.ico).*)"],
\};

Middleware rules:

  • Runs on EVERY matched request (keep it fast, under 200ms)
  • Cannot access the database directly (Edge Runtime limitations)
  • Use for: redirects, rewrites, header injection, auth token checks
  • Do NOT use for: complex data fetching, heavy computation

Common Routing Anti-Patterns

  1. Putting API logic in page.tsx - Use route.ts for API endpoints
  2. Deeply nested layouts that cannot be escaped - Use route groups to break out
  3. Missing default.tsx in parallel routes - Causes 404 on hard refresh
  4. Using redirect() in render - Use it in Server Components or middleware, not Client Components
  5. Over-nesting routes - Keep routes 3-4 levels deep max; use route groups for organization
  6. Client-side routing for auth - Prefer middleware for auth redirects (runs before render)

File Structure Templates

E-commerce App

app/
  (shop)/
    layout.tsx           # Shop layout with nav
    page.tsx             # Home
    products/
      page.tsx           # Product listing
      [slug]/page.tsx    # Product detail
    cart/page.tsx
    checkout/page.tsx
  (account)/
    layout.tsx           # Account layout
    profile/page.tsx
    orders/page.tsx
  @modal/
    default.tsx
    (.)products/[slug]/page.tsx  # Product quick view modal

Dashboard App

app/
  (auth)/
    login/page.tsx
    register/page.tsx
  (dashboard)/
    layout.tsx
    page.tsx
    @sidebar/
      default.tsx
      page.tsx
    @main/
      default.tsx
      [section]/page.tsx
    settings/
      layout.tsx
      page.tsx
      profile/page.tsx
      billing/page.tsx

## Working Example

### Example 1: User asks "How do I create a photo modal pattern?"

**User prompt:** "I want clicking a photo thumbnail to open a modal, but sharing the URL should show the full photo page."

**Skill-guided response would produce this file structure:**

app/ layout.tsx @modal/ default.tsx (.)photo/[id]/page.tsx # Modal version photo/[id]/page.tsx # Full page version gallery/page.tsx # Grid of thumbnails


```tsx
// app/layout.tsx
export default function RootLayout({
  children,
  modal,
}: {
  children: React.ReactNode;
  modal: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        {children}
        {modal}
      </body>
    </html>
  );
}
// app/@modal/default.tsx
export default function Default() {
  return null; // No modal by default
}
// app/@modal/(.)photo/[id]/page.tsx
import { Modal } from "@/components/Modal";
 
export default async function PhotoModal({
  params,
}: {
  params: Promise<{ id: string }>;
}) {
  const { id } = await params;
  const photo = await getPhoto(id);
 
  return (
    <Modal>
      <img src={photo.url} alt={photo.alt} />
      <p>{photo.description}</p>
    </Modal>
  );
}
// app/photo/[id]/page.tsx
export default async function PhotoPage({
  params,
}: {
  params: Promise<{ id: string }>;
}) {
  const { id } = await params;
  const photo = await getPhoto(id);
 
  return (
    <main className="flex min-h-screen items-center justify-center">
      <img src={photo.url} alt={photo.alt} className="max-w-4xl" />
      <h1>{photo.title}</h1>
      <p>{photo.description}</p>
    </main>
  );
}

Example 2: User asks "How should I organize routes for a SaaS app?"

Skill-guided response would recommend:

  • Route groups: (marketing) for public pages, (app) for authenticated dashboard, (auth) for login/signup
  • Parallel routes for dashboard sidebar + main content
  • Middleware for auth redirects
  • Specific file structure with layouts per group

Deep Dive

How the Skill Works

This skill provides Claude with:

  1. Complete file convention reference - Every special file in the App Router and what it does
  2. Decision trees - Structured logic for choosing the right pattern for a requirement
  3. Anti-pattern detection - Common mistakes that the skill proactively warns about
  4. File structure templates - Ready-made architectures for common app types

Customization

Extend this skill by adding:

  • Your project's specific route structure conventions
  • Custom middleware patterns (e.g., A/B testing, feature flags)
  • Organization-specific naming conventions for route groups

How to Install

mkdir -p .claude/skills/nextjs-routing
# Paste the Recipe content into .claude/skills/nextjs-routing/SKILL.md

Gotchas

  • Parallel route slots are positional - If you rename a slot folder, you must update the parent layout props to match.
  • Intercepting routes use filesystem-relative paths - (.) means same level in the filesystem, not URL level. This trips up many developers.
  • default.tsx is mandatory for parallel routes - Without it, a hard refresh on a URL that does not match the slot will render a 404 instead of the fallback.
  • Route groups can create multiple root layouts - If two route groups at the app root each have a layout.tsx, navigation between them causes a full page reload.
  • params is a Promise in Next.js 15+ - You must await params in page and layout components.

Alternatives

ApproachWhen to Use
Pages RouterLegacy Next.js projects not yet migrated
React Router v7Non-Next.js React apps, Remix migration
TanStack RouterType-safe routing with search param validation
Expo RouterReact Native + web file-based routing

FAQs

What is the difference between layout.tsx and template.tsx?
  • layout.tsx wraps children and preserves state across navigations (does not remount)
  • template.tsx wraps children but remounts on every navigation (fresh state each time)
  • Use template.tsx when you need animations or state resets on route change
How do parallel routes work and what is the @folder convention?
  • Parallel routes are defined with @folder (e.g., @modal, @sidebar)
  • Each slot is passed as a prop to the parent layout.tsx
  • Slots render independently and can have their own loading.tsx and error.tsx
  • Slots do NOT affect the URL structure
Gotcha: What happens if you forget default.tsx in a parallel route slot?
  • A hard refresh on a URL that does not match the slot will render a 404 instead of a fallback
  • Always create a default.tsx for every parallel route slot
  • The default.tsx typically returns null when no content should be shown
How does the intercepting routes modal pattern work?
  • Create a parallel route slot (e.g., @modal) with an intercepting route like (.)photo/[id]/page.tsx
  • Soft navigation (Link click) shows the modal version
  • Hard navigation (direct URL visit or refresh) shows the full page at photo/[id]/page.tsx
  • The (.) prefix means "intercept at the same filesystem level"
What do the intercepting route prefixes (.), (..), (..)(..), and (...) mean?
  • (.)folder -- intercepts at the same level
  • (..)folder -- intercepts one level up
  • (..)(..)folder -- intercepts two levels up
  • (...)folder -- intercepts from the app root
How do route groups with parentheses affect the URL?
  • Route groups like (marketing) or (auth) do NOT appear in the URL
  • app/(marketing)/about/page.tsx maps to /about, not /marketing/about
  • Use them to organize routes, apply different layouts, or split root layouts
What are the middleware rules and limitations described on this page?
  • Middleware runs on every matched request -- keep it fast (under 200ms)
  • It cannot access the database directly (Edge Runtime limitations)
  • Use for: redirects, rewrites, header injection, auth token checks
  • Do NOT use for: complex data fetching or heavy computation
Gotcha: What is the difference between [...slug] and [[...slug]] catch-all routes?
  • [...slug] is a required catch-all -- it matches /shop/a/b/c but NOT /shop
  • [[...slug]] is an optional catch-all -- it matches both /docs and /docs/a/b
  • Use optional catch-all when the base path without segments should also be a valid route
How do you type the params prop in a Next.js 15+ page component?
export default async function Page({
  params,
}: {
  params: Promise<{ id: string }>;
}) {
  const { id } = await params;
  // ...
}
  • In Next.js 15+, params is a Promise and must be awaited
What are the common routing anti-patterns listed on this page?
  • Putting API logic in page.tsx instead of route.ts
  • Deeply nested layouts that cannot be escaped (use route groups to break out)
  • Missing default.tsx in parallel routes
  • Using redirect() in Client Components instead of middleware
  • Over-nesting routes beyond 3-4 levels deep
  • Client-side routing for auth instead of middleware
How should you structure routes for a SaaS app with public and authenticated sections?
  • Use route groups: (marketing) for public pages, (app) for the dashboard, (auth) for login/signup
  • Each group can have its own layout.tsx
  • Use middleware for auth redirects between groups
What TypeScript type should the layout component use for parallel route slot props?
export default function RootLayout({
  children,
  modal,
}: {
  children: React.ReactNode;
  modal: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>{children}{modal}</body>
    </html>
  );
}
  • Each @slot folder name becomes a prop typed as React.ReactNode