Skip to content

How to Write an Effective CLAUDE.md File: A Beginner's Guide to Project Context Management

Problem

When I first created a CLAUDE.md file, I wrote it like documentation. I explained folder structures, coding conventions, and project history. I thought Claude needed to “understand” the project the way a new team member would.

Here’s what my first CLAUDE.md looked like:

CLAUDE.md (BEFORE)
# Project Overview
This is a TypeScript React application using Vite for building.
## Folder Structure
- `/src/components` - React components
- `/src/utils` - Utility functions
- `/src/hooks` - Custom React hooks
- `/src/types` - TypeScript type definitions
## Coding Standards
- Use TypeScript for all new files
- Follow ESLint rules
- Write unit tests for utilities
- Use meaningful variable names
- Keep functions small and focused
## Getting Started
1. Install dependencies with `npm install`
2. Run development server with `npm run dev`
3. Build for production with `npm run build`

I thought this was helpful. But Claude kept making the same mistakes:

  • Importing from wrong paths
  • Missing project-specific conventions
  • Using patterns that conflicted with my architecture
  • Asking questions I thought I had answered

It felt like Claude wasn’t reading my file at all.

What happened?

I found a Reddit discussion about CLAUDE.md best practices. The key insight hit me hard:

“Bad CLAUDE.md looks like documentation written for a new hire. Good CLAUDE.md looks like notes you’d leave yourself if you knew you’d have amnesia tomorrow.”

Looking back at my file, I saw the problems:

  • Claude already knows what a components folder is
  • “Use meaningful variable names” tells Claude nothing useful
  • Project history doesn’t help Claude write better code
  • Generic conventions Claude already knows

I was wasting precious instruction budget on things Claude already understood.

Another problem: instruction limits

The Reddit thread also revealed something critical:

“Keep it short. Claude can reliably follow around 150 to 200 instructions at a time.”

Every instruction competes for Claude’s attention. When I filled my CLAUDE.md with generic rules, I was crowding out the stuff that actually mattered.

Here’s what I mean by instruction budget:

instruction-budget.txt
Your CLAUDE.md: ~150 instructions max
Generic conventions: ~80 instructions (wasted)
Project-specific quirks: ~30 instructions (useful)
Rationale/explanations: ~40 instructions (important)
Result: Claude ignores your quirks because generic rules dominate.

The math is simple. If you spend 80 instructions on things Claude already knows, you have only 70 left for what Claude actually needs to know about YOUR project.

The solution

I rewrote my CLAUDE.md following the “amnesia notes” principle. What would I need to know if I forgot everything about this project?

CLAUDE.md (AFTER)
# Working Relationship
- Challenge my assumptions when they seem wrong
- Be direct and concise, no fluff
- Don't add co-author attribution to commits
# Critical Architecture Rules
- All API calls go through `src/services/api.ts` (centralized error handling and auth refresh)
- Never use `useEffect` for data fetching (causes race conditions with our streaming; use TanStack Query)
- `src/components/ui/` contains shadcn primitives only (never modify directly; use `npx shadcn-ui add`)
# Database Conventions
- Migrations in `supabase/migrations/` (run `npm run db:migrate` after schema changes)
- Never drop columns in production (creates downtime; use additive migrations)
- RLS policies required on all tables (enforced in CI)
# Testing Requirements
- 80% minimum coverage (CI fails below this)
- Integration tests for all API routes (unit tests don't catch auth issues)
- E2E tests for checkout flow only (too slow for everything else)
# Weird Stuff You Need to Know
- `NODE_ENV=test` breaks Supabase auth (use `.env.test` with `SUPABASE_DISABLE_DEV=true`)
- Webhook endpoints must return within 10 seconds (Stripe timeout causes duplicate events)
- `src/lib/analytics.ts` is server-side only (client import breaks Next.js middleware)

Notice what’s different:

  • No generic folder structure explanations
  • Every rule is project-specific
  • Each rule explains WHY, not just WHAT
  • The “Weird Stuff” section captures non-obvious gotchas

Why the “why” matters

The Reddit thread emphasized this point:

“Tell it why, not just what. The reason gives Claude context for making judgment calls.”

Consider the difference:

# WITHOUT RATIONALE
- Never use useEffect for data fetching
# WITH RATIONALE
- Never use useEffect for data fetching (causes race conditions in this app's streaming architecture; use React Query instead)

Without the rationale, Claude might think this is a generic style preference. With the rationale, Claude understands there’s a specific technical reason tied to my app’s architecture.

This matters when Claude encounters novel situations. If I say “never use useEffect for data fetching,” Claude might apply this universally. But with the rationale, Claude understands the specific issue (race conditions with streaming) and can make better judgments.

For example, if I later ask Claude to add a simple one-time fetch that doesn’t involve streaming, Claude can reason: “The rule exists because of race conditions with streaming. This fetch doesn’t stream. Maybe useEffect is fine here? I should ask.”

Common mistakes to avoid

I made all these mistakes. Here’s what I learned:

Mistake 1: Documenting generic patterns

# BAD - wastes instruction budget
- Use camelCase for variables
- Use PascalCase for components
- Keep functions under 50 lines
# GOOD - only document exceptions
- Use snake_case for API responses (backend sends snake_case; we transform at service layer)

Claude knows standard conventions. Only document when your project violates them intentionally.

Mistake 2: Writing for a human new hire

# BAD - wrong audience
## Project Overview
This is a Next.js e-commerce application that sells handmade crafts.
The project started in 2023 and has grown to include...
# GOOD - amnesia notes
## Business Context (affects code decisions)
- We sell digital products only (no shipping logic needed)
- Prices include tax (no tax calculation at checkout)
- Currency is always USD (no multi-currency support)

Business context only matters if it affects code decisions. Claude doesn’t need project history.

Mistake 3: Stale or conflicting instructions

# BAD - contradictory
- Use Redux for state management (TODO: migrate to Zustand)
- Always use server components (except for checkout flow which uses client)
# GOOD - current state only
- Use Zustand for state management
- Prefer server components; use client components only when needed for interactivity

If instructions conflict, Claude gets confused. Fix or remove outdated rules immediately.

Mistake 4: Abstract rules without context

# BAD - too vague
- Follow best practices
- Write clean code
- Be consistent
# GOOD - specific and actionable
- All async operations must have error boundaries (we've had 3 production incidents from unhandled promise rejections)
- Components >200 lines should be split (enforced by CI lint rule)
- Date handling always through `date-fns` (native Date causes timezone bugs in our user base)

“Best practices” and “clean code” are too vague. Replace with specific rules that prevent real problems.

The # shortcut for updates

One feature I didn’t know about: pressing # during a conversation adds instructions automatically.

conversation.txt
User: # Never import analytics in client components
Claude: I'll add that to your CLAUDE.md:
- Never import `src/lib/analytics.ts` in client components (causes middleware errors; use `trackEvent()` from `src/lib/client-analytics.ts` instead)

This keeps my CLAUDE.md evolving with real insights from actual work. Instead of trying to anticipate everything upfront, I add rules when Claude makes mistakes.

The practice is:

  1. Claude makes a mistake
  2. I realize there’s a pattern
  3. I press # and add a rule
  4. Claude never makes that mistake again

Over time, my CLAUDE.md becomes a collection of lessons learned from real problems.

How I maintain my CLAUDE.md now

I treat my CLAUDE.md as a living document:

  1. When Claude makes a mistake twice - I add a rule
  2. After each major feature - I review and prune outdated rules
  3. When onboarding to a new project - I start minimal and add as I learn
  4. Before starting work - I quickly scan to refresh context

The goal is not comprehensive documentation. The goal is a cheat sheet that prevents Claude from making the same mistakes I’ve already corrected.

Summary

In this post, I showed how to write an effective CLAUDE.md file. The key principle is treating it like notes for yourself with amnesia, not documentation for a new hire.

The main takeaways:

  1. Keep it under 150-200 instructions (instruction budget is limited)
  2. Document only project-specific quirks, not generic conventions
  3. Include the “why” behind rules (enables better judgment calls)
  4. Use the # shortcut to capture rules when mistakes happen
  5. Update constantly - it’s a living document, not a spec

My CLAUDE.md went from 200 lines of generic documentation to 50 lines of actionable rules. Claude went from “helpful but sometimes wrong” to “actually understands my project.” The difference was not more content, but the right content.

Final Words + More Resources

My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments