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 Routesapp/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
- Putting API logic in page.tsx - Use route.ts for API endpoints
- Deeply nested layouts that cannot be escaped - Use route groups to break out
- Missing default.tsx in parallel routes - Causes 404 on hard refresh
- Using redirect() in render - Use it in Server Components or middleware, not Client Components
- Over-nesting routes - Keep routes 3-4 levels deep max; use route groups for organization
- 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:
- Complete file convention reference - Every special file in the App Router and what it does
- Decision trees - Structured logic for choosing the right pattern for a requirement
- Anti-pattern detection - Common mistakes that the skill proactively warns about
- 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.mdGotchas
- 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 paramsin page and layout components.
Alternatives
| Approach | When to Use |
|---|---|
| Pages Router | Legacy Next.js projects not yet migrated |
| React Router v7 | Non-Next.js React apps, Remix migration |
| TanStack Router | Type-safe routing with search param validation |
| Expo Router | React Native + web file-based routing |
FAQs
What is the difference between layout.tsx and template.tsx?
layout.tsxwraps children and preserves state across navigations (does not remount)template.tsxwraps children but remounts on every navigation (fresh state each time)- Use
template.tsxwhen 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.tsxanderror.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.tsxfor every parallel route slot - The
default.tsxtypically returnsnullwhen 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.tsxmaps 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/cbut NOT/shop[[...slug]]is an optional catch-all -- it matches both/docsand/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+,
paramsis aPromiseand must be awaited
What are the common routing anti-patterns listed on this page?
- Putting API logic in
page.tsxinstead ofroute.ts - Deeply nested layouts that cannot be escaped (use route groups to break out)
- Missing
default.tsxin 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
@slotfolder name becomes a prop typed asReact.ReactNode