Every Claude Code session starts cold. Claude doesn't remember the refactor you did last week, the naming convention you're enforcing, or the fact that you have a custom BaseService class that new services should extend. Without any setup, you'd have to explain your project to Claude at the beginning of every session.
CLAUDE.md solves this. It's a Markdown file that Claude Code reads automatically when it starts — project-level persistent memory that costs you nothing at runtime because it's always there.
What CLAUDE.md is
CLAUDE.md is a plain Markdown file at the root of your project (you can also have one at ~/.claude/CLAUDE.md for user-level preferences that apply across all projects). When Claude Code starts a session, it reads this file and loads it into its context before you type anything.
It's not magic. It's just a file that Claude reads. But because it's read automatically every session, it's the right place to put everything Claude needs to know to be useful in your codebase from the very first message.
The content becomes part of Claude's system context — it shapes how Claude behaves throughout the entire session, not just in response to one prompt.
What belongs in CLAUDE.md
Think of it as onboarding documentation for an extremely capable contractor who has never worked on your project. What would they need to know to be productive immediately?
Your tech stack and versions
## Stack
- Node.js 20, TypeScript 5.4 (strict mode)
- Next.js 15 (App Router), React 19
- PostgreSQL 16 via Prisma ORM
- Tailwind CSS v4
- Vitest for unit tests, Playwright for E2E
This prevents Claude from suggesting patterns that don't apply to your stack (React class components, CommonJS modules, etc.).
Build and test commands
## Common commands
- `npm run dev` — start dev server on :3000
- `npm test` — run unit tests with Vitest
- `npm run test:e2e` — run Playwright tests
- `npm run build` — production build
- `npm run typecheck` — tsc without emit
- `npm run lint` — ESLint + Prettier check
Without this, Claude has to guess your commands or ask you. With it, Claude can run tests after making changes without prompting.
Architecture and conventions
## Architecture
- Services live in `src/services/`. All services extend `BaseService` (src/services/base.ts).
- Database access goes through the Prisma client in `src/lib/prisma.ts` — never import Prisma directly elsewhere.
- API routes live in `src/app/api/`. Use the `withAuth` middleware wrapper for protected routes.
- Client components must be in `src/components/` with the 'use client' directive.
This is the stuff that's obvious to you after 6 months on the project but invisible to anyone reading the code for the first time.
Things to avoid
## Gotchas
- Don't use `console.log` — use the logger in `src/lib/logger.ts` instead.
- The `User` type has two versions: `PublicUser` (safe to send to client) and `User` (includes hashed passwords). Never send `User` to the client.
- Our Postgres setup doesn't support array contains queries via Prisma — use raw SQL for those.
- Don't modify `src/lib/generated/` — these are auto-generated by Prisma.
The gotchas section is often the most valuable part of a CLAUDE.md. These are the things that aren't documented anywhere because they're "obvious" to the team but cause bugs for anyone who doesn't know them.
External services and environment
## External services
- Stripe for payments (test mode in dev, live in production)
- SendGrid for transactional emails — use `src/lib/email.ts`, not the SDK directly
- Redis for session storage and job queues
- Sentry for error tracking (auto-configured, don't add manual try/catch for logging)
What doesn't belong in CLAUDE.md
Be selective. Every line in CLAUDE.md consumes context window space at the start of every session. Content that's obvious, redundant, or outdated is worse than no content — it crowds out useful information and can confuse Claude.
Don't include:
- Things Claude can figure out from reading the code (directory structure it can LS, package.json dependencies it can read)
- Aspirational conventions that don't match the actual codebase yet
- Detailed documentation that exists elsewhere and Claude can read via the Read tool
- Content older than 6 months that might no longer be accurate
The test: if removing a line from CLAUDE.md would cause Claude to make a mistake in this codebase, keep it. If removing it wouldn't change anything, cut it.
User-level CLAUDE.md
You can also put a CLAUDE.md at ~/.claude/CLAUDE.md. This file loads across all your projects and is the right place for:
- Your personal preferences ("I prefer functional over class-based components")
- Your usual tools ("I use Homebrew, not apt")
- Global reminders ("Always ask before installing new npm packages")
Project-level CLAUDE.md takes precedence over the user-level one when there are conflicts. Both are loaded for every session in a project that has one.
Loading additional files with @path imports
If your project is large, you might want to split context across multiple files rather than cramming everything into one CLAUDE.md. You can reference other files with @path/to/file:
## API conventions
@docs/api-conventions.md
## Database schema
@docs/schema-overview.md
Claude automatically reads those files when it encounters these references. This lets you keep CLAUDE.md itself concise while still loading detailed context for specific domains.
This is especially useful for:
- Design system documentation (reference a detailed component conventions file)
- API schemas (reference an OpenAPI spec or a human-readable schema doc)
- Architecture decision records (reference specific ADRs that affect Claude's decisions)
Updating CLAUDE.md during a session
The /memory command lets you update CLAUDE.md without leaving your session:
/memory "Add: We use the Zod library for runtime schema validation on all API inputs. Always import from 'zod', never from '@zod/mini'."
Claude will add this to the appropriate section of CLAUDE.md. On the next session, it'll be there automatically.
You can also ask Claude directly at the end of a session:
We discovered a few non-obvious things about this codebase today. What should I add to CLAUDE.md to help future sessions?
Claude will surface the things worth documenting: the reason the auth middleware must run before rate limiting, the specific format expected by the payment webhook, the test fixture that has to be reset between E2E tests.
This is one of the highest-leverage patterns: using Claude to write its own documentation.
Checkpointing after big sessions
After any session where you made significant architectural decisions or discovered important patterns, spend 2 minutes updating CLAUDE.md. Don't rely on remembering it — the value of CLAUDE.md compounds over time as it accumulates non-obvious project knowledge.
A simple end-of-session workflow:
- Ask Claude what should be added to CLAUDE.md based on the session
- Review its suggestions — it tends to be conservative and include only what's genuinely non-obvious
- Apply the updates via
/memoryor by editing the file directly - Commit CLAUDE.md along with your code changes
The project-level CLAUDE.md should be committed to your repository. It's useful for the whole team, not just you. When a new developer joins, they run Claude Code and immediately get a useful assistant who understands the project.
A complete example
Here's what a mature CLAUDE.md looks like for a medium-sized project:
# Project: Acme API
## Stack
- Node.js 22, TypeScript 5.5 strict mode
- Express 5 with custom middleware stack
- PostgreSQL 16 via Prisma 5
- Redis for caching and session storage
- Vitest for unit tests, Supertest for integration tests
## Commands
- `npm run dev` — start server on :4000 with hot reload
- `npm test` — unit tests
- `npm run test:integration` — integration tests (requires local Postgres + Redis)
- `npm run build` — TypeScript compile
- `npm run typecheck` — type check without compile
- `npm run lint` — ESLint
## Architecture
- All controllers in `src/controllers/`. Each file = one resource. Controllers only call services.
- Business logic in `src/services/`. All services extend BaseService (`src/services/base.ts`).
- Database via `src/lib/db.ts` — never import prisma client elsewhere.
- Auth via JWT. Use `requireAuth` middleware from `src/middleware/auth.ts` on protected routes.
- Errors: throw `AppError` from `src/errors.ts`. Never throw plain Error.
## Key patterns
- Use Zod for all input validation. Schema files live in `src/schemas/`.
- All async route handlers must be wrapped in `asyncHandler` from `src/lib/asyncHandler.ts`.
- Logging via `src/lib/logger.ts`. Never use console.log.
## Gotchas
- Prisma doesn't handle our custom `jsonb` aggregate queries — use raw SQL for those (see `src/lib/rawQuery.ts`).
- The `User` type has `internalUser` (has password hash) and `publicUser` (safe for API responses). Never send `internalUser`.
- Redis keys expire after 24h. Don't assume cached values persist.
- Integration tests require the test database to be migrated: `npm run db:migrate:test`
## External services
- Stripe (use `src/lib/stripe.ts` — never instantiate Stripe client elsewhere)
- SendGrid (use `src/lib/email.ts`)
- Sentry (auto-configured, don't add manual error logging)
## Before writing any code
Read src/services/base.ts to understand the BaseService pattern.
The last section — "Before writing any code" — is particularly useful. It tells Claude to orient itself before starting, which prevents it from writing code that diverges from established patterns.
The context engineering lesson goes deeper on managing what Claude knows and when — including strategies for working with very large codebases where you can't fit everything into one context window.