Claude Code for Next.js — App Router, Server Components & Vercel

Next.js 15 with the App Router introduces new mental models (Server vs Client Components, Server Actions, Route Handlers) that require explicit guidance in CLAUDE.md. Without it, Claude defaults to patterns from older Next.js pages or adds 'use client' everywhere. This guide covers the CLAUDE.md template for Next.js App Router projects, automated build hooks, Server Component conventions, Server Actions, API Routes, and Vercel deployment workflows.

Next.js 15 CLAUDE.md Template

# Project: [Your Next.js App Name]

## Stack
- Next.js 15 (App Router, not Pages Router)
- TypeScript strict mode
- React 19
- Styling: Tailwind CSS v4 (or CSS Modules — specify)
- Database: Prisma + PostgreSQL (or Drizzle + SQLite — specify)
- Auth: NextAuth v5 / Auth.js (or Clerk — specify)
- Package manager: pnpm

## Key commands
```bash
pnpm dev              # development server (http://localhost:3000)
pnpm build            # production build (catches RSC/SSR errors)
pnpm lint             # ESLint
pnpm test             # Vitest or Jest
pnpm typecheck        # tsc --noEmit
```

## App Router conventions (CRITICAL)
- Components are Server Components by default — no state, no effects, can fetch data
- Add "use client" ONLY when component needs: useState/useEffect/hooks, event handlers,
  browser APIs, or third-party client libraries
- Never add "use client" to layout.tsx, page.tsx, or loading.tsx unless required
- Data fetching: fetch() in Server Components with cache options; SWR/TanStack Query
  only in Client Components for client-side mutations
- Server Actions: "use server" directive; live in app/actions/ directory
- Route Handlers: app/api/[route]/route.ts; export GET/POST/PUT/DELETE handlers

## File structure
- app/                     — App Router pages, layouts, and route groups
  - (auth)/                — route group for auth pages (no shared layout)
  - (dashboard)/           — route group for authenticated pages
  - api/                   — Route Handlers
  - actions/               — Server Actions
- components/
  - ui/                    — shared presentational components (Server by default)
  - [feature]/             — feature-specific components
- lib/                     — utilities, db client, auth config
- types/                   — shared TypeScript types

## Patterns
- Error handling: error.tsx for route-level errors; ErrorBoundary for component-level
- Loading: loading.tsx for route-level suspense; Suspense boundaries in components
- Images: always use next/image with width/height or fill + sizes
- Fonts: next/font/google with preload; assign to CSS variable, apply to body
- Metadata: export metadata object from page.tsx; generateMetadata for dynamic routes

Automated Build Hooks

Run type-check + lint after edits so Claude catches RSC boundary violations and TypeScript errors immediately:

// .claude/settings.json
{
  "allowedTools": ["Edit", "Write", "Bash", "Read", "Glob", "Grep"],
  "hooks": [
    {
      "event": "PostToolUse",
      "matcher": "Edit|Write",
      "hooks": [{
        "type": "command",
        "command": "cd $PROJECT_ROOT && pnpm typecheck 2>&1 | tail -20 && pnpm lint --quiet 2>&1 | tail -10"
      }]
    }
  ]
}
Run pnpm build before finalizing any feature. Next.js build catches RSC/Client Component boundary violations, missing Suspense boundaries, and metadata errors that pnpm dev silently ignores.

Server vs Client Component Patterns

PatternCLAUDE.md instruction
Default component type"All components are Server Components unless they need interactivity or browser APIs — never add 'use client' by default"
Data fetching"Fetch data in Server Components using async/await directly in the component body; use unstable_cache for memoization"
Interactive islands"Create a thin 'use client' wrapper around interactive parts; pass server-fetched data as props from the Server Component parent"
Shared state"Use React Context only in Client Components; wrap with a Provider client component in layout.tsx"
Third-party libraries"Wrap client-only libraries (charts, date pickers, rich editors) in a 'use client' component; lazy import with next/dynamic if heavy"
Forms"Use Server Actions for form mutations; useActionState (React 19) for pending/error state in the Client Component form"

New page with data fetching (Server Component)

claude "add a /dashboard/users page using the App Router.
Server Component: fetch users from the database using Prisma.
Use Suspense with a loading skeleton (UserTableSkeleton) while fetching.
Table columns: name, email, role, createdAt (formatted with Intl.DateTimeFormat).
Add a 'New User' button that opens a modal — the modal is a Client Component.
Export metadata with title 'Users | Dashboard'.
File: app/(dashboard)/users/page.tsx"

Server Action for form mutation

claude "add a createUser Server Action in app/actions/users.ts.
Input: { name: string, email: string, role: 'admin' | 'member' }
Validate with Zod — return {success: false, errors} if invalid.
Check email uniqueness; return {success: false, error: 'Email taken'} if duplicate.
Create user with Prisma; hash password with bcrypt (10 rounds).
Call revalidatePath('/dashboard/users') after success.
Return {success: true, userId}.
Use this in a CreateUserForm Client Component with useActionState."

Route Handlers (API Routes)

# CLAUDE.md addition for API Routes

## Route Handler conventions
- File: app/api/[resource]/route.ts
- Export named handlers: GET, POST, PUT, PATCH, DELETE
- Use NextRequest / NextResponse types from 'next/server'
- Auth check at top of every handler; return 401 if unauthenticated
- Return NextResponse.json(data, { status }) — never Response.json()
- Validate request body with Zod before processing
- Error pattern: try/catch → return NextResponse.json({error:msg},{status:500})
claude "add a GET /api/users/[id] route handler.
Auth: check session with auth() from @/lib/auth; 401 if not logged in.
Validate id param is a valid UUID with Zod.
Fetch user from Prisma; return 404 if not found.
Return user data (omit passwordHash).
Add a PATCH handler to update name and role fields.
File: app/api/users/[id]/route.ts"

Middleware & Authentication

# CLAUDE.md addition for middleware

## Middleware (middleware.ts at project root)
- Runtime: Edge (no Node.js APIs like fs, crypto)
- Auth check: read session cookie, verify JWT with jose (Edge-compatible)
- Redirect unauthenticated users to /login for protected routes
- Matcher: config.matcher = ['/((?!_next/static|_next/image|favicon.ico|api/auth).*)']
- Public routes: /, /login, /signup, /api/webhooks/*
claude "add middleware.ts that protects all /dashboard/* routes.
Use NextAuth v5 auth() to check the session.
If no session: redirect to /login with a callbackUrl search param.
If session but user.role !== 'admin' on /dashboard/admin/*: redirect to /dashboard.
Export the matcher config to exclude static assets and auth API routes."

next/image & next/font Patterns

Use caseClaude Code prompt snippet
Fixed-size image"Use next/image with explicit width={800} height={600} alt='...'; add priority prop for above-fold images"
Fill container"Use next/image with fill className='object-cover' on a relative-positioned parent div; provide sizes='(max-width: 768px) 100vw, 50vw'"
Remote image"Add the remote hostname to next.config.ts images.remotePatterns; use next/image with the external URL"
Google Font"Import Inter from next/font/google with subsets:['latin'] and variable:'--font-inter'; apply className to <html> in layout.tsx"
Local font"Use next/font/local pointing to public/fonts/MyFont.woff2; assign variable and use in Tailwind config"

Vercel Deployment Workflows

# CLAUDE.md addition for Vercel deploys

## Deployment
- CLI: vercel (preview) / vercel --prod (production)
- Env vars: managed via Vercel dashboard or `vercel env add NAME value production`
- Build output: Next.js on Vercel uses ISR by default (no extra config needed)
- Edge config: use @vercel/edge-config for feature flags (reads in <1ms)
- Analytics: import { Analytics } from '@vercel/analytics/react' in root layout
- Speed Insights: import { SpeedInsights } from '@vercel/speed-insights/next' in root layout
claude "add a vercel.json that:
1. Sets headers for /api/* routes: Cache-Control: no-store, CORS: *.
2. Adds a redirect from /blog/:slug to /articles/:slug (permanent).
3. Sets the build command to 'pnpm build' and install command to 'pnpm install --frozen-lockfile'.
4. Configures the Node.js runtime to 20.x."

5 Tips for Next.js + Claude Code

  1. Paste your next.config.ts into CLAUDE.md under a "Config" section. Claude will respect your experimental flags, image domains, redirect rules, and custom headers instead of generating incompatible config additions.
  2. Tell Claude your route group structure: "Route groups: (auth) for public pages, (dashboard) for protected pages, (marketing) for landing pages." Claude will place new pages in the correct group and inherit the right layout.tsx.
  3. Specify your exact Prisma schema structure (or paste the schema) in CLAUDE.md so Claude generates correct model relations, correct field names, and knows which fields are optional vs required without hallucinating column names.
  4. For large pages, give Claude one section at a time: "Add just the data fetching logic to this page" → then "Add the table component" → then "Add the filter/sort UI." Generating an entire complex page in one shot produces more hallucinations than incremental steps.
  5. Use next build as a PostToolUse hook for the final verification step. RSC boundary errors (importing a client-only library in a Server Component) only surface at build time — running pnpm dev alone isn't sufficient.
📘 Free: 5 sample Claude Code prompts · plus the £3 pack with 25 moreSee the free 5 →