Your CLAUDE.md is a system prompt for your codebase. Every session you start with Claude Code, Cursor, Windsurf, or Cline, that file gets read first — before you type a single word. It's the difference between an AI that knows your project cold and one that asks you the same questions every time.
Most teams don't have one. The teams that do usually have a bad one. Here's how to write one that actually works.
What CLAUDE.md is (and why it exists)
When you open a project in Claude Code, it automatically reads CLAUDE.md from your project root. Cursor reads .cursorrules. Windsurf reads .windsurfrules. Cline reads .clinerules. Same idea, different filename — a persistent context file that sets up the AI before the conversation starts.
Think of it as a system prompt baked into your repo. It travels with your code. Everyone on your team gets the same AI behavior. New hires get it automatically. The AI doesn't forget your conventions between sessions.
The goal is narrow: tell the AI what it needs to know to work in this codebase without asking you. Not a product overview. Not onboarding docs. Just the stuff that makes AI-generated code fit your project on the first try.
Why most CLAUDE.md files fail
The common failure modes:
Too vague. "Use TypeScript best practices." The AI already knows TypeScript best practices. What it doesn't know is that your project uses zod for all validation and tRPC mutations never return raw errors to the client — that's what goes in the file.
Trying to explain everything. A 3,000-word CLAUDE.md that covers the entire business domain gets ignored in spirit even if it's read. Long context means the AI weights everything equally and nothing specifically.
Treating it like a README. A README explains the project to humans. CLAUDE.md tells the AI how to behave in the project. These are different documents with different audiences.
Never updated. You write it when you start the project, then migrate to Postgres, switch from axios to fetch, drop your ORM, and never touch the file. Now it's actively harmful — the AI confidently follows stale guidance.
No "don't do this" section. The most valuable lines in a CLAUDE.md are often the prohibitions. "Never generate Prisma migrations — always run prisma migrate dev manually." Without that line, the AI will happily generate a migration file for you.
The core structure
A useful CLAUDE.md has six sections. Keep the total file under 400 lines. If it's longer, split it or cut it.
Stack section
Exact versions. Not "React" — React 19.2.3. This matters because the AI's training data contains five years of React docs and Stack Overflow answers. Knowing you're on React 19 changes which patterns it reaches for.
## Stack
- **Framework**: Next.js 15.2 (App Router, TypeScript strict)
- **Database**: PostgreSQL 16 via Prisma 5.20
- **API layer**: tRPC v11 + Zod v3
- **Auth**: NextAuth v5 (beta)
- **Styling**: Tailwind CSS v4
- **Testing**: Vitest + Playwright
- **Node**: 22.x (`.nvmrc` in root)
Development commands
Copy-pasteable, exact commands. The AI will run these or suggest them. If your test command is npm run test:unit -- --watch, write that — not "run the tests."
## Commands
\`\`\`bash
npm run dev # Dev server on :3000
npm run build # Production build
npm run test # Vitest unit tests
npm run test:e2e # Playwright (requires dev server running)
npm run db:push # Push schema changes to dev DB (no migration)
npm run db:migrate # Create + apply migration (production path)
npm run lint # ESLint + type check
\`\`\`
NEVER run \`npm run db:migrate\` unless explicitly asked. Always use \`db:push\` during development.
Directory layout
Not a full tree — just the non-obvious parts. Where does business logic live? Where do you put new API routes? What's the difference between lib/ and utils/?
## Directory layout
\`\`\`
src/
app/ # Next.js App Router pages and layouts
server/
routers/ # tRPC routers — one file per domain (users.ts, posts.ts)
db/ # Prisma client singleton + seed scripts
components/
ui/ # shadcn/ui primitives — DO NOT edit these files
features/ # Feature-specific components (co-located with their router)
lib/ # Pure functions, no framework imports
types/ # Shared TypeScript types and Zod schemas
prisma/
schema.prisma # Single schema file — all models here
migrations/ # Generated by prisma migrate — never hand-edit
\`\`\`
Code conventions
Not "write clean code." The specific patterns your codebase already uses that the AI needs to continue.
## Code conventions
- All database access goes through tRPC procedures. No direct Prisma calls in components.
- Zod schemas live in `src/types/` and are imported by both tRPC router and client forms.
- Server components fetch data directly. Client components receive data as props or use tRPC hooks.
- Error handling: tRPC procedures throw `TRPCError`. Never throw raw `Error` from a router.
- Date handling: always use `date-fns`. Never use `new Date().toLocaleDateString()`.
- Environment variables: access through `src/lib/env.ts` (validated with Zod). Never read `process.env` directly.
- API routes (`app/api/`): only for webhooks and third-party callbacks. All internal API is tRPC.
What not to do
This is the section most CLAUDE.md files skip. It's often the most important one.
## Do not
- Generate Prisma migrations. Run `npm run db:migrate` yourself when schema changes are ready.
- Edit files in `src/components/ui/` — these are managed by shadcn/ui CLI.
- Add new `npm` packages without flagging it. Suggest the package and wait for confirmation.
- Use `any` type. If you're stuck, use `unknown` and narrow it.
- Import from `@prisma/client` directly in components. Use the tRPC client.
- Create `.env` files or commit secrets. Environment setup is in `README.md`.
- Use `console.log` for debugging in production code — use the `logger` from `src/lib/logger.ts`.
Common gotchas
Things that specifically trip up AI assistants in your project. Usually these are framework quirks, third-party library behaviors, or project-specific decisions that look wrong but are intentional.
## Gotchas
- NextAuth v5 uses `auth()` from `next-auth` — not `getServerSession()`. The v4 API is dead.
- tRPC v11 changed the router API. Don't use `.query()` or `.mutation()` — use `.input().query()` chain.
- Tailwind v4: no `tailwind.config.js`. Configuration goes in `globals.css` with `@theme` and `@plugin`.
- Prisma Client is a singleton in `src/server/db/client.ts`. Never call `new PrismaClient()` elsewhere.
- The `useSession()` hook requires the page to be wrapped in `<SessionProvider>` — done in `app/layout.tsx`.
A full example
Here's what a real CLAUDE.md looks like assembled — for a Next.js + Prisma + tRPC project:
# Project context
SaaS dashboard for tracking content performance. Multi-tenant (each workspace is isolated).
Admin users can access all workspaces. Regular users see only their workspace data.
## Stack
- **Framework**: Next.js 15.2 (App Router, TypeScript strict)
- **Database**: PostgreSQL 16 via Prisma 5.20
- **API layer**: tRPC v11 + Zod v3
- **Auth**: NextAuth v5
- **Styling**: Tailwind CSS v4
- **Testing**: Vitest + Playwright
- **Node**: 22.x
## Commands
\`\`\`bash
npm run dev # Dev server :3000
npm run build # Production build
npm run test # Vitest unit
npm run test:e2e # Playwright (requires dev server)
npm run db:push # Sync schema to dev DB (no migration file)
npm run db:migrate # Create migration (ask before running)
npm run lint # ESLint + tsc --noEmit
\`\`\`
## Directory layout
\`\`\`
src/
app/ # Pages, layouts, API routes (webhooks only)
server/
routers/ # tRPC routers — domain-per-file
db/ # Prisma singleton (client.ts)
components/
ui/ # shadcn/ui — do not edit
features/ # Feature components
lib/ # Framework-free utilities
types/ # Zod schemas + TS types
\`\`\`
## Code conventions
- All DB access through tRPC. No Prisma in components.
- Zod schemas in `src/types/` — shared between router input validation and React Hook Form.
- Tenant isolation: every Prisma query MUST include `workspaceId` filter. No exceptions.
- Errors: throw `TRPCError` from routers. Never expose raw DB errors to clients.
- Dates: `date-fns` only. No native Date formatting methods.
- Env vars: import from `src/lib/env.ts`. Never read `process.env` directly.
## Do not
- Edit `src/components/ui/` files.
- Generate migrations — use `db:push` during dev.
- Add packages without flagging them first.
- Use `any` type.
- Skip `workspaceId` filter on any Prisma query.
## Gotchas
- NextAuth v5: use `auth()`, not `getServerSession()`.
- tRPC v11: use `.input(schema).query()` chain, not legacy `.query({ input, resolve })`.
- Tailwind v4: config in `globals.css`, not `tailwind.config.js`.
- `useSession()` needs `<SessionProvider>` — already in root layout.
- Multi-tenancy check happens in tRPC middleware (`src/server/middleware/tenant.ts`),
not in individual routers.
That's about 60 lines. It fits in context comfortably. Every line earns its place.
Keeping it up to date
The file rots fast if you don't have a process.
When to update: Any time you change a major dependency version, add or remove a tool, change a project-wide convention, or discover a new gotcha. The signal is usually "the AI just did the wrong thing confidently" — that's a missing line in your CLAUDE.md.
Who owns it: Treat it like a code file. It lives in version control. Changes get reviewed in PRs. When someone notices the AI is consistently giving bad suggestions in a specific area, that's a PR to add a constraint.
How to test it: Start a fresh session and ask the AI to do something that your CLAUDE.md should prevent or shape. "Add a database query to this component" — does it route through tRPC? If the AI ignores your rules, either the rule is buried too deep or phrased too weakly. Move it up, make it more direct.
Tool-specific notes
Claude Code reads CLAUDE.md. Cursor reads .cursorrules. Windsurf reads .windsurfrules. Cline reads .clinerules. If your team uses multiple tools, you don't want to maintain four files.
The simplest solution: write one canonical file (CLAUDE.md) and symlink the others.
ln -s CLAUDE.md .cursorrules
ln -s CLAUDE.md .windsurfrules
Most tools accept Markdown regardless of filename. Check your tool's docs for exact behavior, but symlinks work in practice for the major editors.
If you need tool-specific sections, use a single file with headers:
## Claude Code specific
- Use the TodoWrite tool for multi-step tasks
- Run builds before considering a task complete
## Cursor specific
- Prefer inline edits over full file rewrites
CLAUDE.md vs README
People conflate these. They serve completely different audiences.
A README explains the project to a new human. It covers what the product does, how to set up a dev environment, how to contribute, what the architecture is. It's onboarding documentation.
A CLAUDE.md tells an AI how to behave in a session. It doesn't need to explain what the product does (the AI can infer that from the code). It needs to specify patterns, prohibitions, and gotchas — things the AI can't reliably infer.
If you find yourself writing a section in CLAUDE.md that a new developer would need but an AI wouldn't, move it to the README. If you find yourself writing a "don't do X" rule in the README, move it to CLAUDE.md.
Nested files for monorepos
Claude Code supports per-directory CLAUDE.md files. This matters for monorepos where a packages/api directory has completely different conventions from packages/web.
monorepo/
CLAUDE.md # Global: workspace commands, repo-wide conventions
packages/
api/
CLAUDE.md # API-specific: Fastify patterns, DB access, don't touch auth
web/
CLAUDE.md # Web-specific: component patterns, state management
shared/
CLAUDE.md # Shared package rules: no framework imports, pure functions only
Claude Code merges these — the root file applies everywhere, directory files apply when working in that directory. Keep the root file focused on things that are truly global. Put the specifics close to the code they govern.
The right mental model
Think of context engineering — you're designing what the AI knows before it starts. A well-written CLAUDE.md is pre-loaded context that eliminates a whole class of back-and-forth.
The best CLAUDE.md files I've seen share a few traits: they're short, they're opinionated, they're current, and they have a clear "don't do this" section. They don't try to teach the AI your business domain — they tell it the rules of the specific game you're playing in this codebase.
Start with the six sections above. Cut anything that the AI could figure out from reading your code. Keep everything that it genuinely can't. Update it when the AI surprises you. After a few weeks you'll have a file that makes every session feel like working with someone who's been on the project for months.



