Skip to content
·11 min read

CLAUDE.md for Next.js: AI Agent Rules for App Router Projects

Claude Code can build Next.js features end-to-end — but only if it knows your App Router patterns, Server Component rules, and data fetching strategy. A well-written CLAUDE.md turns a powerful agent into a precise tool that respects your architecture.

The Next.js Problem: Modern Patterns Aren't Default

Next.js 14 with the App Router is one of the best ways to build modern web applications. Server Components, Server Actions, streaming, incremental static regeneration — it's a powerful toolkit. But it's also a minefield if your AI agent doesn't understand the rules.

Without explicit guidance, Claude Code will write code that works, but in the wrong way. It will use useEffect to fetch data on the client when it should be fetching on the server. It will create Client Components when Server Components would be faster. It will skip validation and error handling. It will mix client and server logic in ways that generate confusing waterfall requests.

A CLAUDE.md file isn't optional for Next.js projects — it's your guardrail. It teaches the agent your specific patterns so that every line of code it writes fits your architecture from the first pass.

Why CLAUDE.md Matters for App Router

The App Router introduced several paradigm shifts from Pages Router:

Server Components are the default. Every component is a Server Component unless you opt in with "use client". This is the opposite of every React developer's muscle memory.

Data fetching happens on the server. There's no getServerSideProps or getStaticProps — you just await inside the component. Client components can't directly hit your database; they have to call Server Actions.

Validation lives in multiple places. Form submission needs client-side validation for UX and server-side validation for security. Middleware can validate auth. Server Actions validate input before touching the database.

Error handling is different. Client-side errors use error.tsx. Server-side errors use try-catch in Server Actions. Streaming errors are tricky to get right.

Without a CLAUDE.md that explains these rules, Claude Code will write code that runs and might even pass tests, but won't be idiomatic Next.js. A good CLAUDE.md prevents that friction.

The Next.js CLAUDE.md Template: Key Sections

Here's what your Next.js CLAUDE.md should cover (beyond the basics in the general Archie template):

1. Component Rules

Make it explicit:

## Component Rules

- Server Components are default. Every component is a Server Component unless marked with "use client".
- Only add "use client" if the component needs: useState, useEffect, useRef, event listeners, or browser APIs.
- Never use "use client" just because you're inside a Client Component parent. If a Server Component doesn't need client features, it stays a Server Component.
- Avoid creating Client Components at high levels of the tree. Keep them as far down as possible.
- Never put database queries, environment variables, or secrets in Client Components.
- Do not use useEffect for data fetching. Use async components and Suspense instead.

2. Data Fetching Patterns

Specify your caching strategy:

## Data Fetching

- For static data: Use `fetch(url, { cache: 'force-cache' })` or ISR with revalidateTag.
- For dynamic data: Use `fetch(url, { cache: 'no-store' })` or Next.js headers/cookies.
- In Server Actions: Always use `'use server'` directive. Never return raw database connections or secrets.
- Always wrap async server operations with try-catch. Return { success: boolean, error?: string, data?: T }.
- For lists: Implement pagination or cursor-based fetching to avoid huge payloads.
- Never use useEffect + fetch on the client. Use Server Actions with form submissions instead.

3. Validation Strategy

Define where validation happens:

## Validation

- Use zod for all validation. Define schemas in a 'lib/schemas.ts' file.
- Client-side: Use react-hook-form + zod for form validation. Show errors inline.
- Server-side: Every Server Action validates input with zod before touching data.
- Middleware: Validate auth tokens and session state. Return 401 if unauthorized.
- Return validation errors as a flat object: { fieldName: "error message" }.
- Never trust client input. Always validate on the server, even if validated on the client.

4. Error Handling

Make error boundaries and fallbacks consistent:

## Error Handling

- Create error.tsx at each segment level for client-side errors.
- Wrap async components in Suspense with a fallback.
- In Server Actions: Catch all errors and return { success: false, error: message }.
- Log errors to your error tracking service (Sentry, LogRocket, etc).
- Never expose internal error details to the client. Return generic "Something went wrong" for unexpected errors.
- Status codes: 400 for validation errors, 401 for auth, 403 for permissions, 500 for server errors.

5. TypeScript Patterns

Enforce type safety:

## TypeScript

- All functions must have explicit return types.
- Define API response types in 'lib/types.ts'. Never use 'any'.
- For async Server Actions, return type is Promise<{ success: boolean; error?: string; data?: T }>.
- Use type guards for discriminated unions. Do not use 'as' casts unless unavoidable.
- Import types with 'import type { ... }' to keep bundle size down.
- Zod schemas are the source of truth. Derive TypeScript types with 'z.infer<typeof schema>'.

Concrete Code Examples to Include

Your CLAUDE.md should include 3–4 copy-paste examples of the exact patterns you want Claude Code to follow:

Example 1: A Server Component with Data Fetching

// app/blog/page.tsx - Server Component with async data fetch
import { Suspense } from 'react';

async function getBlogPosts() {
  const res = await fetch('https://api.example.com/posts', {
    cache: 'force-cache',
    next: { revalidateTag: 'posts' }
  });
  if (!res.ok) throw new Error('Failed to fetch');
  return res.json();
}

function BlogList({ posts }: { posts: any[] }) {
  return (
    <ul>
      {posts.map(post => (
        <li key={post.id}>{post.title}</li>
      ))}
    </ul>
  );
}

export default async function BlogPage() {
  const posts = await getBlogPosts();
  return (
    <div>
      <h1>Blog</h1>
      <Suspense fallback={<p>Loading...</p>}>
        <BlogList posts={posts} />
      </Suspense>
    </div>
  );
}

Example 2: A Server Action with Validation and Error Handling

// app/actions/createPost.ts
'use server';
import { z } from 'zod';
import { db } from '@/lib/db';

const createPostSchema = z.object({
  title: z.string().min(3, 'Title must be at least 3 characters'),
  body: z.string().min(10, 'Body must be at least 10 characters'),
});

export async function createPost(data: unknown) {
  try {
    const validated = createPostSchema.parse(data);
    const post = await db.posts.create({
      data: validated,
    });
    return { success: true, data: post };
  } catch (error) {
    if (error instanceof z.ZodError) {
      return { success: false, error: error.errors[0].message };
    }
    console.error('Create post error:', error);
    return { success: false, error: 'Failed to create post' };
  }
}

Example 3: A Client Component Using a Server Action

// app/components/PostForm.tsx
'use client';
import { useActionState } from 'react';
import { createPost } from '@/app/actions/createPost';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';

export function PostForm() {
  const [state, formAction] = useActionState(createPost, null);
  const { register, handleSubmit } = useForm({
    resolver: zodResolver(/* schema */),
  });

  return (
    <form action={formAction}>
      <input {...register('title')} placeholder="Title" />
      <textarea {...register('body')} placeholder="Body" />
      <button type="submit">Create</button>
      {state?.error && <p className="error">{state.error}</p>}
      {state?.success && <p className="success">Post created!</p>}
    </form>
  );
}

Example 4: A Route Handler (if you use them)

// app/api/posts/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';

const postSchema = z.object({
  title: z.string(),
  body: z.string(),
});

export async function POST(req: NextRequest) {
  try {
    const data = await req.json();
    const validated = postSchema.parse(data);
    // create post in DB
    return NextResponse.json({ success: true }, { status: 201 });
  } catch (error) {
    if (error instanceof z.ZodError) {
      return NextResponse.json(
        { error: error.errors[0].message },
        { status: 400 }
      );
    }
    return NextResponse.json(
      { error: 'Internal server error' },
      { status: 500 }
    );
  }
}

Get the free Next.js CLAUDE.md template

Enterprise-grade conventions for every major stack, plus Claude Code and prompt engineering guides. No account needed.

Download free

Common Mistakes Claude Code Makes Without CLAUDE.md

Here are the patterns you're trying to prevent:

Mistake 1: useEffect for data fetching. Instead of creating async Server Components, Claude writes useEffect(() => fetch(url)) on the client. This is slower, breaks streaming, and requires hydration. A CLAUDE.md saying "Never use useEffect for data fetching" prevents this immediately.

Mistake 2: Mixing client and server logic. Claude puts API calls in Client Components, uses props to pass giant data objects, or tries to call database functions from client code. A rule like "Client Components can only call Server Actions" prevents confusion.

Mistake 3: Skipping validation. Claude writes happy-path code that assumes the input is correct. A clear validation section that says "Every Server Action validates input with zod before touching data" ensures that doesn't happen.

Mistake 4: Forgetting error handling. Forms don't report errors back to the user. API routes return 500 instead of 400 for validation errors. A structured error handling section prevents this.

Mistake 5: Making too many "use client" components. Without guidance, Claude marks components as Client Components unnecessarily, bloating your JS bundle and hurting performance.

Structure Your CLAUDE.md File

Here's the full structure your CLAUDE.md should follow:

# Claude Instructions for Next.js

## Read This First
1. Read `memory/MEMORY.md` for project status and context.
2. Read `memory/project-context/tech-stack.md` (lists Next.js version, database, auth library).
3. Read `memory/patterns/code-patterns.md` (naming rules, file structure).

## Next.js Architecture Overview
[2-3 paragraphs describing: App Router structure, Server vs Client strategy, data fetching approach, validation layers]

## Component Rules
[Explicit rules about Server Components, "use client", etc.]

## Data Fetching Patterns
[How to fetch data on the server, caching strategy, Server Actions]

## Validation
[Where validation happens, zod usage, error format]

## Error Handling
[error.tsx files, try-catch patterns, error logging]

## TypeScript Patterns
[Type safety rules, return types, type inference from zod]

## Examples
[4 code examples as shown above]

## Rules
- Never use useEffect for data fetching. Use async/await and Suspense.
- Always validate input on the server before touching the database.
- Keep Client Components as far down the tree as possible.
- Every function has an explicit return type.
[... more rules ...]

Testing and Enforcement

Once you have a CLAUDE.md, you can optionally add linting rules that enforce the patterns you describe:

ESLint rules: Use @next/eslint-plugin-next to catch client-side data fetching and other anti-patterns.

Custom rules: If your project has specific patterns (e.g., "all API responses must be wrapped in a result type"), add a custom ESLint rule and reference it in CLAUDE.md.

Code review checklist: Add a checklist to your PR template that matches your CLAUDE.md rules. Claude Code will see it and respect it during implementation.

These aren't strictly necessary — Claude Code will follow your CLAUDE.md without automated enforcement. But they make human code review faster and prevent the rare case where Claude misunderstands the rules.

CLAUDE.md sets the rules. Archie runs the workflow.

Persistent memory, role-based skills, and approval gates. From idea to merged PR.

View pricing

Getting Started

If you're building a Next.js project and plan to use Claude Code, invest an hour now writing a CLAUDE.md. Include the sections above, add your own patterns and examples, and commit it to your repo. Whenever you work with Claude Code, it will read this file and adapt.

Don't have a template? Download the free Next.js CLAUDE.md template and customize it for your project. It includes all the sections above, plus a memory system and a skill definition for your dev agent.

Or if you want the full workflow — memory, skills, approval gates, multi-service support — Archie includes the complete setup with a Next.js-specific template. Either way, the principle is the same: structure beats luck.