Claude Code React Next.js CLAUDE.md

Claude Code for React and Next.js: CLAUDE.md Templates and Development Patterns (2026)

The Prompt Shelf ·

React and Next.js projects accumulate conventions fast. Within a few weeks a codebase has opinions about naming, state management, styling, testing, data fetching — and new contributors (human or AI) start making inconsistent choices the moment those opinions aren’t written down anywhere.

Claude Code reads your CLAUDE.md before touching a single file. That makes it the right place to encode the conventions that matter most, so every component, API route, and test Claude Code generates lands in the style your project already uses rather than the style the model prefers by default.

This guide covers everything needed to set up Claude Code for serious React and Next.js development: copy-paste CLAUDE.md templates, component generation conventions, TypeScript and Tailwind patterns, testing with Vitest and Playwright, and team AGENTS.md configurations. Most of the content applies to both App Router and Pages Router; where they differ, we call it out explicitly.

What Claude Code Gets Wrong Without a CLAUDE.md

The default behavior is not bad, but it makes decisions that may not match your project:

  • It will use interface and type interchangeably instead of your chosen convention
  • It will add "use client" to every component with state, even when RSC composition would keep it server-side
  • It will generate API routes in the format it last saw in training data, which may not match your router
  • It will pick useState for state that should live in Zustand, or reach for Zustand when local state is fine
  • It will name files Button.tsx, button.tsx, or Button/index.tsx based on whatever the current file context suggests

A CLAUDE.md that’s three hundred lines long solves all of this. Every prompt Claude Code runs gets that context. You stop reviewing AI output for style violations and start reviewing it for logic.

App Router CLAUDE.md Template

This template covers Next.js 14+ with the App Router, TypeScript strict mode, Tailwind CSS, and Vitest. Copy it, adjust the paths and dependency choices, and commit it to the project root.

# Project: [Your App Name]

## Stack
- Next.js 14+ (App Router)
- TypeScript 5+ (strict mode)
- Tailwind CSS v3
- Vitest + Testing Library for unit/integration tests
- Playwright for E2E tests
- Zustand for global state
- Prisma for database access (if applicable)

## Directory Structure
src/
  app/                    # Next.js App Router pages and layouts
    (auth)/               # Route group: auth pages
    (dashboard)/          # Route group: authenticated app
    api/                  # API route handlers
  components/
    ui/                   # Primitives: Button, Input, Modal, etc.
    features/             # Feature-scoped components
    layouts/              # Layout components
  lib/                    # Utilities, helpers, constants
  hooks/                  # Custom React hooks
  stores/                 # Zustand stores
  types/                  # Shared TypeScript types
  server/                 # Server-only logic (never imported by client)

## File Naming
- React components: PascalCase filename (`UserCard.tsx`)
- All other files: kebab-case (`use-auth.ts`, `date-utils.ts`)
- Route handlers: `route.ts` inside `app/api/` directories
- Page files: `page.tsx`, layout files: `layout.tsx`
- Test files: co-located as `ComponentName.test.tsx`

## Component Conventions
- Export components as named exports, NOT default exports
- Props interface named `ComponentNameProps` in the same file
- Place props interface immediately before the component function
- No React import needed (React 17+ JSX transform is configured)

Example:
```tsx
interface UserCardProps {
  user: User;
  onSelect?: (id: string) => void;
}

export function UserCard({ user, onSelect }: UserCardProps) {
  // ...
}

”use client” / “use server” Rules

  • Default: components are Server Components. Do NOT add “use client” unless required.
  • Add “use client” ONLY when the component uses: useState, useEffect, useReducer, event handlers (onClick, onChange, etc.), browser APIs, or third-party hooks that require client context.
  • Never add “use client” to: components that only fetch data, layout components that don’t handle interaction, pure display components with no interactivity.
  • Add “use server” to async functions that run exclusively on the server and are called via forms or server actions. Place these in server/ or in a file with the "use server" directive at the top.
  • Push “use client” boundaries as high as possible. Prefer wrapping only the interactive sub-component rather than the whole page/section.

Barrel Exports

  • Create index.ts in each component directory to export public API
  • Re-export from the directory, not from nested files
  • Do NOT create barrel exports for app/ directories (breaks tree-shaking)

TypeScript Rules

  • strict mode is on. Never disable it for a single file.
  • No any. Use unknown for truly unknown types, narrow with type guards.
  • No non-null assertion (!) unless you can document in a comment why it’s safe.
  • Prefer interface for object shapes. Use type for unions, intersections, and primitives.
  • Use satisfies for config objects to get type-checking without widening.
  • Generic type parameters: single uppercase letter for simple cases (T), descriptive names for complex cases (TData, TError).

State Management

  • Local UI state (open/closed, hover, form field focus): useState
  • Form state: React Hook Form (already installed)
  • Server state (data from API): React Query / SWR (already installed)
  • Global client state (user preferences, auth, cart): Zustand
  • Do NOT use Context for state that changes frequently — it re-renders every consumer
  • Do NOT reach for Zustand for state that is purely local to one component tree

Zustand Store Pattern

// stores/use-auth-store.ts
import { create } from 'zustand';
import { persist } from 'zustand/middleware';

interface AuthState {
  user: User | null;
  setUser: (user: User | null) => void;
}

export const useAuthStore = create<AuthState>()(
  persist(
    (set) => ({
      user: null,
      setUser: (user) => set({ user }),
    }),
    { name: 'auth-storage' }
  )
);

Tailwind CSS Rules

  • Use Tailwind utility classes directly in JSX. No custom CSS unless absolutely necessary.
  • Use cn() from lib/utils.ts (clsx + tailwind-merge) to combine conditional classes.
  • Never use arbitrary values (w-[317px]) unless the value comes from a design token.
  • Component variants should use cva() from class-variance-authority, not ternaries.
  • Responsive prefixes order: base → sm → md → lg → xl → 2xl
  • Dark mode: use dark: prefix classes, not separate dark-mode files.

API Routes (App Router)

Route handlers live in app/api/[route]/route.ts. Pattern:

import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';

const bodySchema = z.object({
  name: z.string().min(1),
});

export async function POST(request: NextRequest) {
  const body = await request.json();
  const result = bodySchema.safeParse(body);
  
  if (!result.success) {
    return NextResponse.json(
      { error: result.error.flatten() },
      { status: 400 }
    );
  }

  // handler logic
  return NextResponse.json({ success: true });
}

Always validate request bodies with Zod. Always return typed responses via NextResponse.json. Never throw from route handlers — catch errors and return appropriate status codes.

Data Fetching

  • In Server Components: fetch directly with async/await, no useEffect needed
  • In Client Components: use React Query (useQuery, useMutation)
  • Wrap all fetch calls in a try/catch or use a fetchWithError utility
  • Use Next.js cache() for server-side function memoization across request
  • Use revalidatePath() / revalidateTag() after mutations, not full page reloads

next/image Rules

  • Always use next/image for images. No <img> tags.
  • Always provide width and height for non-fill images.
  • Use fill + container with position: relative for responsive images.
  • Always provide meaningful alt text. Empty alt="" for decorative images only.

Testing (Vitest + Testing Library)

  • Test files co-located with source: ComponentName.test.tsx
  • Test behavior, not implementation: render component, interact, assert output
  • Never test internal state directly
  • Use screen queries in this priority: ByRole > ByLabelText > ByText > ByTestId
  • Mock at module boundaries, not at function level
  • Do NOT test styling (class names) — test that the component renders and behaves correctly

Unit test template:

import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { UserCard } from './UserCard';

const mockUser = { id: '1', name: 'Alice', email: '[email protected]' };

describe('UserCard', () => {
  it('renders user name', () => {
    render(<UserCard user={mockUser} />);
    expect(screen.getByText('Alice')).toBeInTheDocument();
  });

  it('calls onSelect with user id when clicked', async () => {
    const onSelect = vi.fn();
    render(<UserCard user={mockUser} onSelect={onSelect} />);
    await userEvent.click(screen.getByRole('button', { name: /alice/i }));
    expect(onSelect).toHaveBeenCalledWith('1');
  });
});

E2E Tests (Playwright)

  • E2E tests live in e2e/ at project root
  • Use Page Object Model for complex pages
  • Never test the same behavior in both Vitest and Playwright — unit for logic, E2E for user journeys
  • Use data-testid sparingly; prefer accessible selectors (role, label, text)
  • Run E2E in CI only; do not run locally on every change

.claudeignore

.next/ node_modules/ .vercel/ out/ dist/ *.generated.ts prisma/migrations/ coverage/ playwright-report/

Do Not

  • Do not generate mock data inline in components — use fixtures in __fixtures__/
  • Do not import server-only modules in client components (will error at runtime)
  • Do not use export default for components
  • Do not add console.log to committed code
  • Do not use // @ts-ignore or // @ts-nocheck
  • Do not mutate Zustand state directly — always use the store’s setter functions

## Pages Router CLAUDE.md Template

If the project uses the Pages Router (Next.js 12-13 or a legacy codebase not yet migrated), the conventions differ enough to warrant a separate template. The key differences are data fetching patterns, API routes, and the absence of Server Components.

```markdown
# Project: [Your App Name]

## Stack
- Next.js 13 (Pages Router)
- TypeScript 5+ (strict mode)
- Tailwind CSS v3
- Jest + Testing Library for unit/integration tests
- Playwright for E2E tests
- Zustand for global state

## Directory Structure
src/
  pages/              # Next.js Pages Router pages
    api/              # API routes (pages/api/)
  components/
    ui/               # Primitives
    features/         # Feature-scoped components
  lib/                # Utilities
  hooks/              # Custom hooks
  stores/             # Zustand stores
  types/              # Shared types

## File Naming
- React components: PascalCase (`UserCard.tsx`)
- All other files: kebab-case (`use-auth.ts`)
- Pages: kebab-case to match URL segments (`user-profile.tsx` → /user-profile)
- API routes: kebab-case inside pages/api/ (`pages/api/user-settings.ts`)

## Data Fetching
- getServerSideProps: for pages that need fresh data on every request
- getStaticProps + getStaticPaths: for statically generated pages
- SWR (already installed): for client-side data fetching and revalidation
- No useEffect + fetch patterns — use SWR for client data

## API Routes (Pages Router)
API routes live in `pages/api/`. Pattern:

```ts
import type { NextApiRequest, NextApiResponse } from 'next';
import { z } from 'zod';

const bodySchema = z.object({ name: z.string() });

type ResponseData = { success: true } | { error: string };

export default function handler(
  req: NextApiRequest,
  res: NextApiResponse<ResponseData>
) {
  if (req.method !== 'POST') {
    return res.status(405).json({ error: 'Method not allowed' });
  }

  const result = bodySchema.safeParse(req.body);
  if (!result.success) {
    return res.status(400).json({ error: 'Invalid request body' });
  }

  res.status(200).json({ success: true });
}

Component Conventions

[Same as App Router template above] Note: No “use client” / “use server” directives needed in Pages Router. Every component is a client component by default.

TypeScript, State, Tailwind, Testing

[Same rules as App Router template]


The Pages Router template is deliberately shorter because it has fewer decisions to encode. The real complexity in modern Next.js development is navigating the App Router's server/client boundary, which the longer template addresses.

## Component Generation Conventions in Depth

### File Naming That Claude Code Will Follow

The naming section in CLAUDE.md is not decoration. When Claude Code creates a new file, it reads the naming rules and applies them. Without the rules, it defaults to the naming convention it saw most recently in your codebase — which is inconsistent across large teams.

The most important conventions to specify:

**Components are PascalCase files.** `UserCard.tsx`, not `user-card.tsx`. This matches the component name and makes imports predictable.

**Non-component files are kebab-case.** `use-auth.ts`, `date-utils.ts`, `api-client.ts`. This includes hooks, utilities, stores, and constants.

**Test files are co-located, not in a separate `__tests__` directory.** `UserCard.test.tsx` sits next to `UserCard.tsx`. Co-location makes it obvious when tests are missing and easier to find the test for any given file.

**Index barrel files are at the directory level.** If you have a `components/ui/` directory, there is a `components/ui/index.ts` that re-exports everything. Imports throughout the codebase use `from '@/components/ui'` rather than `from '@/components/ui/Button'`.

### "use client" Strategy

This is the single most impactful convention to encode for App Router projects. Without guidance, Claude Code adds `"use client"` conservatively — often to any component that has an `onClick` handler, even when those handlers could live inside a smaller child component.

The rule to encode: **push `"use client"` boundaries as close to the interactivity as possible.**

A page that displays user data and has one interactive filter button should not be a Client Component. The page should be a Server Component that renders the data directly. The filter button — and only the filter button — should be extracted into a small Client Component with the `"use client"` directive.

This pattern:
1. Keeps data fetching on the server (faster, no loading states)
2. Reduces the client-side JavaScript bundle
3. Makes the performance characteristics of each component explicit in the file

Tell Claude Code this explicitly in CLAUDE.md and it will structure new components accordingly instead of defaulting to `"use client"` on anything that touches interactivity.

### Props Interface Pattern

Encoding the props interface pattern prevents a large category of style inconsistencies:

```typescript
// This is what CLAUDE.md should produce:
interface UserCardProps {
  user: User;
  variant?: 'compact' | 'full';
  onSelect?: (id: string) => void;
}

export function UserCard({ user, variant = 'full', onSelect }: UserCardProps) {
  // ...
}

The convention here: interface named ComponentNameProps, defined immediately before the component function, in the same file. Not in a separate types file unless the type is shared across multiple components.

Testing Setup and Philosophy

Vitest Configuration

Vitest is faster than Jest for most Next.js projects and shares the Jest API, so migration is usually straightforward. The key config for Next.js:

// vitest.config.ts
import { defineConfig } from 'vitest/config';
import react from '@vitejs/plugin-react';
import tsconfigPaths from 'vite-tsconfig-paths';

export default defineConfig({
  plugins: [react(), tsconfigPaths()],
  test: {
    environment: 'jsdom',
    globals: true,
    setupFiles: ['./src/test/setup.ts'],
  },
});
// src/test/setup.ts
import '@testing-library/jest-dom';
import { cleanup } from '@testing-library/react';
import { afterEach } from 'vitest';

afterEach(() => {
  cleanup();
});

Include this config in your CLAUDE.md’s testing section so Claude Code generates vi.fn() instead of jest.fn(), and uses vitest imports rather than jest imports.

What to Test

Tell Claude Code explicitly what warrants a unit test versus what doesn’t. Without this guidance it tends to generate tests for everything, including trivial rendering checks that add no value and break when styling changes.

The guidance we encode:

Write tests for:

  • Components with conditional rendering based on props or state
  • Components with user interaction (forms, buttons, keyboard navigation)
  • Custom hooks with logic
  • Utility functions with edge cases
  • Error states and loading states

Skip tests for:

  • Static display components that render props directly with no logic
  • Simple wrapper components
  • Pages (covered by E2E tests)
  • CSS class names and styling

Playwright for User Journeys

Playwright tests belong in e2e/ and should represent user journeys, not implementation details. The Page Object Model pattern keeps tests readable as the UI evolves:

// e2e/pages/dashboard.page.ts
import { type Page, type Locator } from '@playwright/test';

export class DashboardPage {
  readonly heading: Locator;
  readonly createButton: Locator;

  constructor(private page: Page) {
    this.heading = page.getByRole('heading', { name: 'Dashboard' });
    this.createButton = page.getByRole('button', { name: 'Create new' });
  }

  async goto() {
    await this.page.goto('/dashboard');
  }

  async createItem(name: string) {
    await this.createButton.click();
    await this.page.getByLabel('Item name').fill(name);
    await this.page.getByRole('button', { name: 'Save' }).click();
  }
}

Include this pattern in CLAUDE.md so when Claude Code generates new E2E tests, it creates Page Objects rather than inline locator soup.

Team AGENTS.md for React Projects

When multiple developers use Claude Code on the same project, AGENTS.md files add another layer of context that applies to specific directories. Unlike CLAUDE.md at the project root, AGENTS.md files can be scoped to subtrees.

A components/AGENTS.md for a shared component library:

# Component Library Agent Guidelines

## Component Creation Checklist
When creating a new component in this directory:
1. Add the component to the directory's index.ts barrel export
2. Create a co-located test file (ComponentName.test.tsx)
3. Add a Storybook story if the component has more than one variant
4. Document all props with JSDoc comments

## Accessibility Requirements
All interactive components must:
- Have correct ARIA roles
- Support keyboard navigation (Tab, Enter, Space, Escape)
- Work correctly with screen readers (test with axe-core in tests)
- Have visible focus indicators (do not remove :focus-visible styles)

## Do Not Add
- Business logic to UI components
- Data fetching to UI components
- Direct Zustand store access in ui/ components (pass data via props)

## Variant Naming
Use `cva()` for variants. Variant names should match the design system:
- size: 'sm' | 'md' | 'lg'
- variant: 'primary' | 'secondary' | 'ghost' | 'destructive'

An app/api/AGENTS.md for the API layer:

# API Routes Agent Guidelines

## Route Handler Pattern
Every route handler must:
1. Validate the request method explicitly
2. Validate request body/params with Zod
3. Return consistent error shapes: { error: string } | { errors: ZodError }
4. Log errors server-side before returning error responses
5. Never expose internal error messages or stack traces in responses

## Authentication
- Import auth helpers from @/server/auth (never from @/lib)
- Check authentication before any data access
- Return 401 for unauthenticated, 403 for unauthorized (not both as 401)

## Database Access
- Database calls go through the repository layer in @/server/repositories
- Never call Prisma directly from route handlers
- Wrap all database operations in try/catch

.claudeignore for React Projects

A .claudeignore file tells Claude Code which files and directories to exclude from its context. For Next.js projects the critical exclusions are:

# Build outputs — never relevant to code generation
.next/
out/
dist/
build/

# Dependencies — large and irrelevant to application logic
node_modules/

# Deployment artifacts
.vercel/
.netlify/

# Generated files — editing these is pointless
*.generated.ts
*.generated.tsx
prisma/migrations/
graphql/generated/

# Test coverage reports
coverage/
playwright-report/
test-results/

# Environment files — not code, not context
.env
.env.local
.env.production

The node_modules/ exclusion is the most impactful. Without it, Claude Code may scan package source code when trying to understand a library’s API, which wastes context window and produces worse results than simply reading the package’s TypeScript declarations.

Practical Workflow: Component-Driven Development

Here is how the Claude Code workflow looks in practice for a Next.js App Router project with the CLAUDE.md in place.

Start with the domain type. Before generating any component, ask Claude Code to define the TypeScript interface for the data. Example prompt: “Add a Project interface to src/types/project.ts. A project has an id, name, description, status (active/archived/draft), owner (User reference), and createdAt.”

This gets reviewed and committed first. Everything built after it derives from this type.

Generate the server data layer. Once the type exists: “Create a getProjects server function in src/server/repositories/projects.ts that fetches all projects for the current user from the database. Use the Project type from types/.”

Generate the Server Component page. “Create the projects list page at app/(dashboard)/projects/page.tsx. It should be a Server Component that calls getProjects from the server layer and renders a ProjectList component (which does not exist yet — create a stub that accepts projects: Project[]).”

Fill in the client interaction. “The ProjectList component needs a filter bar that lets users filter by status. Extract just the filter bar into a Client Component (ProjectFilter.tsx with "use client"). The list itself should remain a Server Component stub for now.”

This sequence respects the App Router’s architecture, keeps the client boundary narrow, and produces files that conform to the naming and convention rules in CLAUDE.md. Each step produces a small, reviewable diff rather than a massive code dump.

Common Mistakes to Address in CLAUDE.md

Based on patterns we see across React codebases, these are the gaps most likely to cause trouble if left unspecified:

export default vs named exports. Claude Code will use export default for components unless told otherwise. Named exports are almost universally better for React components because they enable auto-import, refactoring tools work more reliably, and barrel exports stay consistent. Specify this explicitly.

Error boundary placement. Without guidance, Claude Code won’t add error boundaries at all. Specify where they should go: typically around route segments, around data-fetching components, and around third-party embeds.

Loading states. App Router has loading.tsx files for Suspense-based loading UI. Without guidance in CLAUDE.md, Claude Code may generate manual loading state management with useState inside client components instead. Specify that loading UI goes in loading.tsx files at the route level.

Environment variable validation. Specify that environment variables should be validated at startup using a Zod schema in lib/env.ts, and that process.env.X should not appear raw in application code.

Integrating with the Broader Rules Collection

The patterns in this guide are part of a larger ecosystem of Claude Code configuration. If you are managing rules across multiple projects or sharing configurations across a team, our rules collection has reusable CLAUDE.md snippets organized by framework, testing library, and workflow pattern.

For React projects specifically, look at the entries tagged with react, nextjs, and typescript-strict — they cover additional edge cases around internationalization (next-intl), authentication (next-auth / Clerk), and CMS integration (Contentful, Sanity) that go beyond the scope of this guide.

What a Production CLAUDE.md Actually Looks Like

The templates above are starting points, not final documents. A production CLAUDE.md for a mature Next.js project is usually longer — between three hundred and six hundred lines — because it encodes decisions the team made over months: which Radix UI primitives are wrapped and customized (don’t generate new ones), which Zod schemas are shared (import from lib/schemas/ don’t redefine), which Server Actions are available (don’t generate new fetch calls when an action exists), which feature flags are checked via (use the useFeatureFlag hook from lib/flags).

The right way to build a CLAUDE.md is incrementally. Start with the template above. The first time Claude Code generates something that doesn’t match the team’s conventions, add a rule. After three months of active development, the CLAUDE.md becomes one of the most valuable files in the repository — a living specification of how the project works, useful for onboarding humans and AI agents alike.

The alternative is reviewing AI output for style violations indefinitely. Encoding conventions in CLAUDE.md turns review from style correction into logic review — which is the work that actually matters.

FAQ

Does Claude Code work with both the Next.js App Router and Pages Router?

Yes. Claude Code works with both routers — the key is telling it which one your project uses in CLAUDE.md. Most conventions (naming, TypeScript, Tailwind, testing) are shared; data fetching and API route patterns differ, so use a router-specific template section for those.

Where should CLAUDE.md go in a Next.js project?

In the project root, next to package.json, committed to version control. Claude Code reads it automatically at the start of every session, so every contributor and every AI-generated change gets the same conventions.

How do I stop Claude Code from adding "use client" to every component?

Encode an RSC-first rule in CLAUDE.md: components are Server Components by default, and "use client" is added only for useState, useEffect, event handlers, browser APIs, or client-only third-party hooks. Also instruct it to push the "use client" boundary to the smallest interactive sub-component instead of the whole page.

Should a React team use CLAUDE.md or AGENTS.md?

CLAUDE.md is read by Claude Code specifically, while AGENTS.md is the cross-tool standard supported by 28+ AI coding tools. Teams where everyone uses Claude Code can standardize on CLAUDE.md; mixed-tool teams should keep shared conventions in AGENTS.md and reference it from CLAUDE.md.

How long should a production CLAUDE.md be for a Next.js app?

Mature Next.js projects typically land between 300 and 600 lines. Start from a template, then add a rule each time Claude Code generates something that doesn’t match your team’s conventions.

What TypeScript rules should CLAUDE.md enforce in a React project?

The highest-leverage rules: strict mode always on, no any (use unknown plus type guards), no non-null assertions without a documented reason, interface for object shapes and type for unions, and satisfies for config objects. These are the areas where AI-generated TypeScript drifts most.


Deployment: Where Claude Code Projects Go Live

Once you have a working Next.js project with a solid CLAUDE.md, deployment is the next decision. Two platforms dominate the space:

Netlify connects to GitHub and deploys on every push. The free tier covers most indie and side-project traffic, and features like deploy previews, branch deploys, and edge functions integrate cleanly with Next.js App Router. For projects where Vercel’s pricing scales uncomfortably with traffic, Netlify is the standard alternative.

Vercel remains the default for Next.js given its first-party support, but the pricing model changes at scale. For teams shipping with Claude Code, Netlify’s predictable pricing and straightforward CI/CD often wins for long-running projects.

If your project uses a static export (next export), both platforms work equally well. For full App Router with Server Components and Server Actions, test your build pipeline early — the netlify.toml configuration is minimal, but worth validating before launch.

Related Articles

Explore the collection

Browse all AI coding rules — CLAUDE.md, .cursorrules, AGENTS.md, and more.

Browse Rules