Monorepo & Multi-Directory CLAUDE.md Setups
A single, flat CLAUDE.md works fine for a small, cohesive codebase. It works poorly for a monorepo with a dozen packages owned by different teams, using different frameworks, with different conventions โ cramming all of that into one file either makes it enormous (every session pays the context cost of every packageโs conventions, even when working in just one) or forces it into such generic language that it stops being useful for any individual package. The fix is the layered discovery mechanism covered in How Claude Code Discovers & Loads CLAUDE.md Files, applied deliberately.
The Layering Strategy
monorepo/โโโ CLAUDE.md โ truly shared: monorepo tooling, workspaceโ commands, cross-cutting conventionsโโโ packages/โ โโโ api/โ โ โโโ CLAUDE.md โ API-specific: framework, databaseโ โ conventions, service boundariesโ โโโ web/โ โ โโโ CLAUDE.md โ frontend-specific: component conventions,โ โ state management approachโ โโโ shared-ui/โ โโโ CLAUDE.md โ design-system-specific: component APIโ conventions, accessibility requirementsThe root file should contain only whatโs genuinely true across the entire monorepo โ the workspace tool (turbo, nx, pnpm workspaces), how packages relate to each other at a high level, and any cross-cutting rule that applies everywhere (a shared linting/formatting standard, for instance). Everything package-specific belongs in that packageโs own file.
What Goes at the Root
# Monorepo Root CLAUDE.md
## Workspace Commands- Install everything: `pnpm install`- Run a specific package's scripts: `pnpm --filter <package-name> <script>`- Run affected tests only (based on git diff): `pnpm turbo test --filter=...[main]`
## Structure- `packages/api/` โ Express backend, owned by the platform team- `packages/web/` โ Next.js frontend, owned by the product team- `packages/shared-ui/` โ shared component library, consumed by `web`
## Cross-Cutting Conventions- All packages use TypeScript strict mode- Commit messages follow Conventional Commits (`feat:`, `fix:`, `chore:`)- Never publish `shared-ui` changes without a changeset (`pnpm changeset`)This orients an agent working anywhere in the repo โ even someone whoโs never worked in packages/api/ specifically still benefits from knowing the workspace commands and overall structure.
What Goes in Package-Level Files
## Stack- Express + Prisma + PostgreSQL
## Conventions- Route handlers live in `src/routes/`, one file per resource- Database access always goes through the repository layer in `src/repos/` โ never call Prisma directly from a route handler- All endpoints require the `authenticate` middleware unless explicitly marked public in the route registration
## Testing- Integration tests use a real test database (`npm run test:db:setup` first)- Unit tests for pure business logic in `src/services/`Someone working purely in packages/web/ never pays the context cost of these API-specific details โ they simply arenโt loaded when working outside that subtree.
Avoiding Duplication Across Levels
The most common mistake in monorepo setups is restating root-level information inside package files โfor completeness.โ Since subdirectory files layer additively on top of the root file rather than replacing it, this is pure waste:
<!-- Redundant โ already covered by the root CLAUDE.md -->We use TypeScript strict mode across the monorepo.All commits follow Conventional Commits.
<!-- Better: package files assume root-level context and add only what's new --># packages/api/CLAUDE.md## API-Specific Conventions- Database access goes through `src/repos/`, never direct Prisma calls in route handlersOwnership and Review
In a monorepo with multiple teams, itโs worth treating each packageโs CLAUDE.md the same way youโd treat that packageโs other configuration โ owned by the team that owns the package, reviewed in PRs that touch it, and not edited freely by contributors from other teams without at least a heads-up, since it encodes that teamโs actual working conventions.
Common Mistakes
One giant root-level CLAUDE.md instead of layering. This is the exact problem monorepo layering exists to solve โ if your root file has grown to include every packageโs specific conventions, split it before it becomes an unmanageable, session-bloating single document.
Package-level files that repeat root-level content. Wastes context budget for the same reason repeating any information across levels does โ package files should assume the root fileโs content is already known and add only whatโs genuinely additional.
No root-level file at all, only package-level ones. This leaves cross-cutting workspace commands and structure undocumented for anyone working across package boundaries, or for someone new to the monorepo who hasnโt yet worked inside any specific package.
Frequently Asked Questions
How deep can the subdirectory nesting go? As deep as your actual directory structure โ the same discovery mechanism applies at any depth, so a deeply nested package structure (packages/backend/services/billing/) can have its own file if that level genuinely has distinct conventions worth isolating.
Should every package have its own CLAUDE.md, even small ones? Not necessarily โ a package with no conventions meaningfully different from what the root file already covers doesnโt need its own file; add one only once thereโs genuinely package-specific content worth isolating.
What if two packages have conflicting conventions that both need explicit statement? State each explicitly in its own package file, and note the divergence in whichever is the exception (โunlike other packages, this one uses X because Yโ) so the reasoning is visible, not just the difference.
Summary
Monorepos are where CLAUDE.mdโs layered, additive discovery earns its keep: a lean root file for genuinely cross-cutting context, and package-level files for whatโs specific to each subtree โ avoiding both an unmanageable single mega-file and the context bloat of loading every packageโs conventions regardless of where youโre actually working.