In Chapter 2, you learned what a CLAUDE.md is and why it matters. Now you build one from scratch.
Not from /init output. Not from a template. From understanding your project. The difference matters. A CLAUDE.md generated by /init describes what the tool can see. A CLAUDE.md you write describes what the tool needs to know.
Step 1: Commands
Start here because it is the easiest section and immediately useful.
List every command you run more than once a week. Not every command that exists — every command you actually use. If it is in your package.json scripts but you have not run it in a month, skip it.
## Commands
pnpm dev # Start dev server on :3000
pnpm dev:fresh # Clean .next cache first, then dev server
pnpm build # Production build — run before every PR
pnpm lint # ESLint across all packages
pnpm gen:types # Regenerate Supabase TypeScript types
pnpm test:e2e # Playwright E2E tests (requires dev server running)Every command gets a comment. Not because you need it — because Claude needs it. The comment tells the agent when to use each command. "Production build" tells Claude this is not something to run casually during development. "Requires dev server running" prevents a class of errors.
Step 2: Structure
Top-level directories only. What each one does. Five to ten lines maximum.
The agent reads your file tree anyway. It can see every directory. This section is not a directory listing — it is orientation. It tells Claude what role each part of the codebase plays so it does not have to figure that out from file contents.
## Structure
apps/web/ # Next.js 14 app (pages, components, styles)
packages/db/ # Supabase client + generated types
packages/shared/ # Utilities, schemas, AI integration
packages/ui/ # shadcn/ui primitives + custom components
packages/api/ # TRPC routers + client setupFive lines. Done. Do not document subdirectories. Do not list every file. If you feel the urge to write more, resist it. Depth belongs in skills, not CLAUDE.md.
Step 3: Boundaries
This is the most important section. This is where 80% of agent mistakes are prevented.
Boundaries define what can import what. What runs on the server vs the client. What depends on what. Without explicit boundaries, Claude will create circular imports, use server-only code in client components, and import from internal paths instead of public APIs.
## Boundary Rules
- `packages/` NEVER imports from `apps/`
- `@jd/db` and `@jd/shared` have zero internal deps
- `@jd/ui` depends only on `@jd/shared`
- `@jd/api` depends only on `@jd/db` and `@jd/shared`
- Always use package public API: `import { X } from '@jd/ui'`
- Never deep import: `import { X } from '@jd/ui/src/button'`
## Server/Client Split
| Package | Client-safe | Server-only |
|-------------|----------------------|---------------------------|
| `@jd/db` | Types only | `@jd/db/server` |
| `@jd/shared`| cn(), schemas, data | `@jd/shared/server` (AI) |
| `@jd/api` | trpc hook, types | `@jd/api/server` (router) |Notice the pattern: each rule tells Claude what NOT to do. "Never deep import" prevents an entire category of errors. "NEVER imports from apps" prevents dependency direction violations. Boundaries are prohibitions, not descriptions.
Step 4: Anti-patterns
The "never do this" list. Every rule here was learned from a real mistake. This is not generic advice — it is a record of things Claude has actually gotten wrong on your codebase.
## Critical Anti-Patterns
- **Never** import `@jd/db/server` in client components — uses next/headers
- **Never** import `@jd/shared/server` in client components — server-only deps
- **Never** call AI providers directly — always use sendWithFallback()
- **Never** modify `packages/db/src/database.types.ts` manually — run pnpm gen:types
- **Never** create REST API routes for data TRPC already handles
- **Never** skip Zod validation on TRPC inputsEach rule follows the same structure: Never do X — because Y. The "because" is essential. It gives Claude the reasoning, which helps it generalize. "Never import @jd/db/server in client components" is a rule. Adding "uses next/headers" turns it into understanding — now Claude knows to watch for any server-only API in client code, not just this specific import.
Do not add generic rules. "Never write bad code" teaches nothing. "Never use any type" might be relevant but it is enforced by TypeScript strict mode already. Only add rules that solve problems your linter and type system cannot catch.
Step 5: Where new code goes
A decision table. Two columns. Zero ambiguity.
This prevents Claude's most common structural mistake: putting code in the wrong place. Without this table, Claude has to infer where things belong from existing file patterns. It usually gets it 70% right. With the table, it is 99%.
## Where Does New Code Go?
| Type | Package | Example |
|-------------------|------------------|----------------------------|
| UI primitive | `@jd/ui` | Button, Card, Dialog |
| Zod schema | `@jd/shared` | blogArticleSchema |
| Utility function | `@jd/shared` | cn(), calculateReadingTime |
| TRPC router | `@jd/api` | New data endpoint |
| Supabase migration| `apps/web/supabase/` | New table/column |
| Page/layout | `apps/web/app/` | New route |
| Feature component | `apps/web/components/` | Admin panel, blog |This table is the single most referenced section in my CLAUDE.md. Every time Claude creates a new file, it checks this table. The precision of your table directly correlates with how often Claude puts code in the right place.
The context budget check
You have written five sections. Now count lines.
wc -l CLAUDE.md
If you are over 200 lines, it is time to cut. Go through every line and ask one question: would Claude make a mistake without this line?
If the answer is no — delete it. If the answer is "maybe, but rarely" — move it to a skill. If the answer is "yes, and it would be a serious mistake" — keep it.
I went through this exercise on my own CLAUDE.md and cut 40% of the content. Most of what I removed was reference information — things Claude could figure out from the code. What remained was rules — things Claude could not infer.
Layered rule hierarchy
Your CLAUDE.md does not have to be a single file. Claude Code supports a layered hierarchy that lets you separate concerns.
~/.claude/CLAUDE.md # User-level: your personal preferences
# "I prefer functional components"
# "Use TypeScript strict mode"
.claude/CLAUDE.md # Project-level: shared with team via git
# Team conventions, CI rules
CLAUDE.md (repo root) # Repo-level: checked into the repo
# Architecture, boundaries, commands
Rules cascade — more specific overrides less specific. Your personal preference for named exports lives at the user level. Your team's convention for error handling lives at the project level. Your repo's import boundaries live at the repo level.
Do not duplicate rules across levels. If the boundary rules are in the repo-level CLAUDE.md, do not repeat them in the project-level file. Duplication means two places to update when something changes — and one of them will be forgotten.
The result
When you finish, you should have a CLAUDE.md that is:
- Under 200 lines. Preferably under 150.
- Project-specific. Every rule reflects your actual codebase, not generic best practices.
- Actionable. Every line either prevents a mistake or directs a decision.
- Maintainable. You can read it in two minutes and know if anything needs updating.
If you want to see what this looks like for different stacks, open-source example CLAUDE.md files exist for SaaS Next.js apps, Go microservices, Django APIs, Laravel APIs, and Rust projects. Each one follows the five-section structure with stack-specific rules. The patterns transfer — the structure is the same, only the rules change.
This file is the foundation of your blueprint. Everything else — skills, hooks, memory, verification — builds on top of it. Get this right and the rest falls into place. Get this wrong and no amount of advanced configuration will compensate.