If you’ve ever onboarded a new developer to your codebase, you know the pain. Hours of context-setting, tribal knowledge transfers, and that inevitable moment when someone asks “wait, why did we do it this way?” Now imagine that instead of a human developer, you’re onboarding an AI coding assistant — one that’s capable, fast, and eager to help, but completely blind to everything your team has learned the hard way.
That’s exactly the problem CLAUDE.md solves.
This special configuration file is the secret weapon that separates developers who use Claude Code as a glorified autocomplete from those who turn it into a genuine engineering partner. Think of it as a persistent memory layer — a document that tells Claude everything it needs to know about your project before it writes a single line of code. Done right, it transforms your repository from a collection of files into a project with a brain.
In this guide, we’ll break down exactly how to write a CLAUDE.md that actually works.
What Is CLAUDE.md and Why Does It Matter?
When Claude Code starts a session in your repository, it reads CLAUDE.md automatically. No prompting required. No copy-pasting documentation into the chat. The file lives at the root of your project, and its contents become part of Claude’s working context from the very first interaction.
This matters for a simple reason: context is everything in AI-assisted development.
Without context, Claude might suggest a React component when your team has standardized on Vue. It might write a database query that bypasses your custom ORM layer. It might propose a perfectly reasonable solution that violates an architectural decision you made eighteen months ago for reasons that are no longer obvious from the code alone.
With a well-crafted CLAUDE.md, none of that happens. Claude understands your constraints, your preferences, your conventions, and your history. It becomes genuinely fluent in your project — not just your codebase.
The investment you make in writing this file pays dividends on every single subsequent interaction.
The Anatomy of an Effective CLAUDE.md
Not all CLAUDE.md files are created equal. A file that just lists your tech stack is better than nothing, but it’s barely scratching the surface. The most effective versions communicate intent, constraints, and culture — not just tools.
Here’s the framework that consistently produces the best results.
1. Project Overview and Purpose
Start with the “elevator pitch” for your project. What does it do? Who uses it? What problem does it solve?
This sounds obvious, but it does critical work. When Claude understands the purpose of your application, it makes better architectural suggestions, writes more appropriate error messages, and understands which edge cases are likely to matter.
## Project OverviewThis is a B2B SaaS platform for logistics companies to manage last-mile delivery routing. Our primary users are operations managers who need real-time visibility into driver locations and delivery status. Uptime and data accuracy are non-negotiable — a wrong delivery status can trigger costly business decisions downstream.
Notice how that last sentence does more than describe the app — it communicates stakes. Claude now understands that cutting corners on error handling is not acceptable in this codebase.
2. Tech Stack and Architecture Decisions
List your technologies, but go further. Explain why you made the choices you did, especially when they’re non-obvious.
## Tech Stack- **Backend**: Node.js with Express (not NestJS — we evaluated it but the team's familiarity with plain Express outweighed the benefits)- **Database**: PostgreSQL with Prisma ORM- **Frontend**: React 18 with TypeScript (strict mode enabled)- **State Management**: Zustand (we migrated away from Redux in Q2 2023 — do not suggest Redux patterns)- **Testing**: Vitest for unit tests, Playwright for E2E
The parenthetical notes are doing important work here. They prevent Claude from confidently suggesting patterns you’ve already evaluated and rejected. That’s institutional knowledge that would otherwise evaporate the moment someone closes their laptop.
3. Code Style and Conventions
This is where many developers underinvest, and where the payoff is enormous. Your style conventions section should cover:
- Naming conventions: camelCase vs. snake_case, component naming patterns, file naming
- File structure: Where things live and why
- Comment philosophy: Do you prefer self-documenting code? Or extensive JSDoc comments?
- Import ordering: Does it matter to your linter?
- Error handling patterns: Custom error classes? Specific logging approaches?
## Code Conventions### Naming- React components: PascalCase, one per file- Utility functions: camelCase, grouped by domain in `/utils`- Constants: SCREAMING_SNAKE_CASE only for true application-wide constants- Database models: PascalCase matching Prisma schema names### Error HandlingWe use a custom `AppError` class that wraps all thrown errors with a `code`, `statusCode`, and `isOperational` flag. Never throw raw Error objects. All async route handlers use the `asyncWrapper` utilityto ensure errors are properly forwarded to the Express error middleware.### CommentsWe prefer self-documenting code over comments. Add comments only when explaining *why* something is done a specific way, not *what* it does.
4. Development Workflows and Commands
Tell Claude how your project actually runs. This sounds mechanical, but it prevents a surprising number of frustrating interactions.
## Development Setup### Running the Project- `npm run dev` — starts both API server and frontend with hot reload- `npm run dev:api` — API server only (port 3001)- `npm run dev:client` — Frontend only (port 3000)### Testing- `npm test` — runs all unit tests- `npm run test:e2e` — requires running `npm run dev` first in a separate terminal- `npm run test:coverage` — generates coverage report in `/coverage`### Database- Migrations live in `/prisma/migrations` — never edit them directly- `npm run db:migrate` — applies pending migrations- `npm run db:seed` — seeds development database with fixture data- Always run migrations before seeding after a fresh clone### Important: Never run `npm run db:reset` in any environment unless you have explicit confirmation — it drops all tables.
That last warning isn’t paranoia. It’s the kind of guardrail that prevents an AI assistant (or a tired human) from making an expensive mistake.
5. Architecture Constraints and Anti-Patterns
This section is the secret sauce of a truly great CLAUDE.md. Every mature codebase has a list of things that seem reasonable but are actually landmines. Document them explicitly.
## Architecture Constraints### What Not to Do- **No direct database calls from components** — all data fetching goes through custom hooks in `/hooks` that use our API layer- **No inline styles** — we use Tailwind CSS exclusively; CSS modules are acceptable for complex animations only- **No `any` types** — TypeScript strict mode is enforced; if you're tempted to use `any`, it's a signal to refactor- **No business logic in route handlers** — routes should be thin; logic belongs in service files under `/services`- **No synchronous file operations** — always use `fs.promises` or the `async` alternatives### Architectural Decisions- We use the Repository pattern for all database access — see `/src/repositories/README.md` for details- Authentication is handled entirely by the `AuthMiddleware` — do not reimplement auth logic anywhere else in the codebase- All external API calls go through service classes that use our centralized `HttpClient` wrapper, which handles retry logic and logging
This section alone can save hours of back-and-forth corrections in your AI-assisted sessions.
6. Current Focus and Known Issues
This is an often-overlooked section that adds enormous value. Tell Claude what you’re actively working on and what rough edges currently exist.
## Current State (Updated: January 2025)### Active DevelopmentWe're currently migrating from our legacy REST API to a new GraphQL layer. When suggesting new features, prefer GraphQL implementations. The REST endpoints in `/api/v1` are being deprecated — don't add new ones.### Known Technical Debt- The `/src/utils/dateHelpers.ts` file is a mess — we know. It's scheduled for refactor in Q1. Work around it rather than extending it.- The `UserProfile` component has a known re-rendering issue we haven't resolved yet — avoid adding hooks to it until the underlying issue is addressed.### Performance SensitivityThe delivery route calculation in `/services/routing` is performance-critical. Avoid any changes that add synchronous processing or blocking operations to that path.
This context transforms Claude from a code generator into something closer to a project stakeholder.
Advanced Techniques for Power Users
Once you’ve got the fundamentals covered, a few advanced techniques can take your CLAUDE.md from good to exceptional.
Use Conditional Instructions
You can scope instructions to specific types of tasks:
## Task-Specific Guidelines### When Writing Tests- Always test edge cases and error states, not just happy paths- Use factory functions from `/tests/factories` for test data — never hardcode IDs or emails- Prefer testing behavior over implementation details### When Reviewing Code- Flag any deviation from the Repository pattern as a blocking issue- Performance concerns in hot paths should be marked as high priority- Security-related findings should always be called out explicitly
Include Examples of What “Good” Looks Like
Rather than just describing conventions, show them:
## Code Examples### Preferred: Service Layer Patterntypescript// services/userService.tsexport class UserService {constructor(private userRepo: UserRepository) {}async updateEmail(userId: string, newEmail: string): Promise {await this.validateEmailUniqueness(newEmail);return this.userRepo.update(userId, { email: newEmail });}}### Avoid: Business Logic in Routestypescript// ❌ Don't do thisrouter.post('/users/:id/email', async (req, res) => {const existing = await db.user.findFirst({where: { email: req.body.email }});if (existing) throw new Error('Email taken');// …logic that belongs in a service});
Concrete examples remove ambiguity in a way that descriptions simply can’t.
Keep It Maintained
A CLAUDE.md that’s six months out of date is worse than none at all — it actively misleads. Treat it like living documentation. Add updates to your pull request checklist. When a team decision changes, the file changes.
Consider adding a timestamp or version marker at the top so it’s obvious when the file was last reviewed.
Common Mistakes to Avoid
Even developers who understand the value of CLAUDE.md often undermine their own efforts with these common pitfalls.
- Being too vague. “Write clean code” tells Claude nothing. “Follow the Single Responsibility Principle, with functions under 30 lines where possible” is actionable.
- Documenting the obvious. If it’s already in your linter config or enforced by TypeScript, you don’t need to repeat it here. Save the real estate for things that can’t be automated.
- Writing it once and forgetting it. Your project evolves. Your CLAUDE.md needs to evolve with it. Stale instructions create confusion.
- Making it too long. Context windows have limits, and more importantly, dense walls of text reduce clarity. Aim for comprehensive but scannable. Use headers, bullets, and code blocks liberally.
- Forgetting the “why.” The single biggest upgrade you can make to any section is adding a brief explanation of the reasoning behind a decision. “We use Zustand instead of Redux because…” is infinitely more useful than just “We use Zustand.”
The ROI of a Well-Written CLAUDE.md
Let’s be concrete about what you’re actually buying with this investment.
A thoughtful CLAUDE.md means fewer corrections per session. It means suggestions that fit your actual codebase rather than a generic TypeScript project. It means Claude catches potential issues against your specific constraints rather than just general best practices. It means new team members — human or AI — can start contributing meaningfully faster.
More subtly, it forces a kind of organizational clarity that’s valuable in its own right. The act of writing down your architectural decisions, your conventions, and your known issues surfaces assumptions you didn’t know you were making. Teams that invest in a great CLAUDE.md often report that the process of writing it was itself useful — a forced articulation of things that had only existed as shared, unspoken knowledge.
Conclusion
The gap between developers who get mediocre results from AI coding assistants and those who get transformative results often isn’t about prompting skill or technical sophistication. It’s about context. It’s about whether the AI understands your project deeply enough to actually help rather than just respond.
CLAUDE.md is your mechanism for closing that gap.
Start with the basics — a project overview, your tech stack, your key conventions. Then layer in the nuance: the architectural decisions, the anti-patterns, the current focus, the known rough edges. Add examples. Explain the why. Keep it maintained.
Done well, your CLAUDE.md becomes something more than documentation. It becomes a genuine artifact of your team’s engineering culture — a distillation of everything your project knows about itself, made available to every collaborator who needs it, immediately and automatically.
Your repository has knowledge. It’s time to give it a brain.
Have a CLAUDE.md pattern that’s worked particularly well for your team? Share it in the comments — the best practices in this space are still being written.
