Prelude

Consider a monorepo with fourteen packages. A React frontend, a Node.js API, a shared TypeScript library, a CLI tool, three microservices in Go, infrastructure-as-code in Terraform, and a handful of utility packages that glue everything together.

Each package has its own build system, its own testing patterns, its own conventions. The initial approach is often a single CLAUDE.md file in the root directory. Enormous. Over four hundred lines of instructions covering every package, every build command, every coding convention.

It works, technically. Claude Code reads it and follows the instructions. But it is slow to load, eats a significant chunk of the context window, and most of the information is irrelevant to whatever happens to be the focus at any given moment.

Claude Code does not just read one CLAUDE.md file. It walks the directory tree. It reads a root-level file for shared context and subdirectory files for package-specific context. It supports a .claude/rules/ directory for glob-scoped rules. It even has a local variant, CLAUDE.local.md, for personal preferences that should not be committed to version control.

Once this hierarchy is understood, the entire CLAUDE.md setup can be restructured. The root file drops to sixty lines of shared conventions. Each package gets its own focused file. Rules handle file-type-specific behaviour. The result is faster sessions, more relevant context, and Claude Code that behaves appropriately no matter which part of the monorepo is being worked on.

This guide is the complete playbook for structuring CLAUDE.md files in a monorepo. If you have more than a few packages in a single repository, this will save you weeks of trial and error.

The Problem

Monorepos are complicated. They contain multiple packages with different languages, different build tools, different testing approaches, and different coding conventions. A React component library has different rules than a Go microservice. A Terraform module has different expectations than a Node.js API.

Claude Code needs to understand these differences. When you ask it to write a test in your React package, it should know to use Jest and React Testing Library. When you ask it to write a test in your Go service, it should know to use the standard testing package with table-driven tests. When you ask it to modify your Terraform configuration, it should know your naming conventions and state backend setup.

A single CLAUDE.md file cannot handle this well. If you put everything in one file, most of the content is noise for any given task. Claude Code loads the entire file into its context window at session start, which means hundreds of lines about your Go microservices are consuming context tokens while you are working on your React frontend. Worse, conflicting instructions from different packages can confuse the model. "Always use semicolons" for your JavaScript and "never use semicolons" for your Go tests are both valid rules, but they conflict when they appear in the same file without clear scoping.

The other extreme, having no CLAUDE.md at all and relying on Claude Code to infer conventions from the code, also fails. Claude Code is good at reading code, but it cannot infer your team's preferences about error handling patterns, commit message formats, or which testing framework to use when multiple options are available. Some things need to be stated explicitly.

The solution is a structured hierarchy of CLAUDE.md files that gives Claude Code the right context at the right time, without wasting tokens on irrelevant information.

The Journey

Understanding the Loading Order

Before you can structure your CLAUDE.md files effectively, you need to understand how Claude Code discovers and loads them. The loading order determines which instructions Claude sees and when.

Claude Code reads CLAUDE.md files from multiple locations, in this order.

First, the user-level file at ~/.claude/CLAUDE.md. This contains personal preferences that apply to every project. Things like "I prefer British English in comments" or "always explain your reasoning before showing code." This file is not part of your repository and is not shared with your team.

Second, the root CLAUDE.md in your project directory. This is the file that sits next to your package.json, go.mod, or whatever defines the root of your monorepo. Claude Code always reads this file, regardless of which subdirectory you are working in.

Third, ancestor directory CLAUDE.md files. Claude Code walks up the directory tree from your current working directory to the project root, reading any CLAUDE.md files it finds along the way. If you start Claude Code in packages/api/, it reads CLAUDE.md files from packages/api/, packages/, and the project root. Subdirectory CLAUDE.md files below your current location load on demand when Claude reads files in those directories, not eagerly at session start.

Fourth, .claude/rules/ files. These are glob-scoped rule files that activate based on the file paths Claude Code is working with. More on these in a later section.

The key insight is that all of these files are additive. Claude Code concatenates them into a single context. It does not replace or override. This means your root file and subdirectory files should complement each other, not repeat each other.

The Root CLAUDE.md for Shared Conventions

Your root CLAUDE.md file is the one every Claude Code session reads, regardless of where in the monorepo the developer is working. This makes it valuable real estate. Every line in this file consumes context tokens for every session. Keep it lean and universal.

Here is what belongs in a root CLAUDE.md for a monorepo.

# MyCompany Monorepo

## Repository Structure
- `packages/web/` - React frontend (TypeScript, Vite)
- `packages/api/` - Node.js API (TypeScript, Express)
- `packages/shared/` - Shared TypeScript library
- `services/auth/` - Authentication service (Go)
- `services/billing/` - Billing service (Go)
- `infra/` - Terraform infrastructure

## Shared Conventions
- Branch naming: `feature/TICKET-123-short-description`
- Commit messages: conventional commits (feat:, fix:, chore:, docs:)
- All code must pass CI before merging. Run `make check` to verify locally.
- Never commit secrets. Use environment variables from `.env.local` (gitignored).
- British English in all documentation and comments.

## Monorepo Commands
- `make build` - Build all packages
- `make test` - Run all tests
- `make lint` - Lint all packages
- `make check` - Run build, test, and lint

## Package-Specific Instructions
Each package has its own CLAUDE.md with build commands and conventions.
Check the CLAUDE.md in the package directory before making changes.

Notice what is not in this file. There are no package-specific build commands, no language-specific coding standards, no testing framework details. Those belong in the subdirectory files. The root file provides the map. The subdirectory files provide the territory.

Aim for under a hundred lines in the root file. If you are going over that, you are probably including package-specific details that should live closer to the code they describe.

Package-Level CLAUDE.md Files

Each package in your monorepo should have its own CLAUDE.md file with instructions specific to that package. This is where you get detailed and specific.

Here is an example for a React frontend package.

# Web Frontend

## Build and Test
- Dev server: `npm run dev` (from this directory)
- Build: `npm run build`
- Test: `npm test`
- Test single file: `npm test -- path/to/test.ts`
- Lint: `npm run lint`
- Type check: `npx tsc --noEmit`

## Tech Stack
- React 19 with TypeScript
- Vite for bundling
- Tailwind CSS for styling
- React Query for server state
- Zustand for client state
- Jest + React Testing Library for tests

## Coding Standards
- Functional components only. No class components.
- Use named exports. No default exports.
- Co-locate tests with source files: `Button.tsx` and `Button.test.tsx` in the same directory.
- Use `interface` for component props, not `type`.
- All components must have a corresponding test file.
- Prefer composition over complex props. Break large components into smaller ones.

## Directory Structure
- `src/components/` - Reusable UI components
- `src/pages/` - Page-level components (one per route)
- `src/hooks/` - Custom React hooks
- `src/api/` - API client and query definitions
- `src/store/` - Zustand store definitions
- `src/types/` - Shared TypeScript types

And here is an example for a Go microservice.

# Auth Service

## Build and Test
- Build: `go build ./...`
- Test: `go test ./...`
- Test with coverage: `go test -coverprofile=coverage.out ./...`
- Run locally: `go run cmd/auth/main.go`
- Lint: `golangci-lint run`

## Coding Standards
- Standard Go project layout. cmd/ for entry points, internal/ for private packages.
- Table-driven tests for all functions with multiple input/output combinations.
- Error wrapping with `fmt.Errorf("context: %w", err)`.
- No global state. Pass dependencies through function parameters or struct fields.
- Context propagation through all function chains. First parameter is always `ctx context.Context`.
- Use `slog` for structured logging. No `fmt.Println` in production code.

## Database
- PostgreSQL with `pgx` driver.
- Migrations in `migrations/` directory, numbered sequentially.
- Never modify existing migrations. Always create new ones.

## API
- gRPC with Protocol Buffers. Proto files in `proto/` directory.
- Run `buf generate` after modifying proto files.
- All RPC methods must have request validation.

Each package file is self-contained. A developer working in the Go auth service does not need to know about React Testing Library, and vice versa. Claude Code loads only the relevant files based on where the developer is working.

The .claude/rules/ Directory for Glob-Scoped Rules

Sometimes you need rules that apply to specific file types rather than specific directories. That is where .claude/rules/ comes in. This directory sits in your project root, and each file in it contains a glob pattern in its frontmatter that determines which files the rules apply to.

Here is an example. Create a file at .claude/rules/typescript-tests.md.

---
paths: ["*.test.ts", "*.test.tsx", "*.spec.ts", "*.spec.tsx"]
---

When writing or modifying TypeScript tests, follow these rules.

- Use `describe` and `it` blocks, not `test` blocks.
- Each `describe` block should correspond to a function or component being tested.
- Use `beforeEach` for shared setup. Avoid `beforeAll` unless the setup is genuinely expensive.
- Mock external dependencies. Never make real API calls or database queries in tests.
- Use `jest.fn()` for function mocks and `jest.spyOn()` for method mocks.
- Assert specific values, not just truthiness. Use `toEqual` over `toBeTruthy`.
- Include at least one test for the happy path, one for error handling, and one for edge cases.

And another for Terraform files at .claude/rules/terraform.md.

---
paths: ["*.tf", "*.tfvars"]
---

When working with Terraform files, follow these rules.

- Use `terraform fmt` style. Two-space indentation.
- All resources must have a `tags` block with at least `Environment` and `ManagedBy` tags.
- Use data sources to reference existing resources. Never hardcode ARNs or IDs.
- Variables must have `description` and `type` fields. Include `default` only if the variable is optional.
- Outputs must have `description` fields.
- Use modules for repeated patterns. Never copy-paste resource blocks.

The glob patterns support standard syntax. *.rs matches all Rust files. packages/web/**/*.tsx matches all TSX files in the web package. **/migrations/*.sql matches SQL migration files anywhere in the repository.

Rules files are powerful because they activate automatically based on context. When Claude Code edits a test file, it gets the testing rules. When it edits a Terraform file, it gets the infrastructure rules. No manual switching required.

We typically recommend creating rules files for three to five file types that have distinct conventions in the codebase. More than that and you risk overloading the context. The goal is to capture the rules that developers frequently forget or that Claude Code gets wrong without explicit guidance.

CLAUDE.local.md for Personal Preferences

Every developer has preferences that are personal, not organisational. Maybe one developer likes verbose explanations while another prefers terse responses. Maybe one developer wants Claude Code to always suggest tests while another only wants tests when asked.

CLAUDE.local.md is the answer. This file sits in the project root alongside CLAUDE.md, but it is gitignored. Each developer can create their own version with personal preferences that do not affect the team.

# My Preferences

- When explaining code, be concise. Skip the preamble.
- I prefer to see the full file after edits, not just the diff.
- When I ask for a refactor, suggest tests for the refactored code.
- I use vim keybindings, so do not suggest VS Code shortcuts.

Add CLAUDE.local.md to your .gitignore file at the root of the monorepo. This ensures personal preferences stay personal. The system prompts vs CLAUDE.md guide explains the broader distinction between shared configuration and personal preferences in more detail.

The Line-Length Guideline

Claude Code loads CLAUDE.md files in full regardless of length. There is no truncation. However, shorter files produce better adherence because they keep the most important instructions prominent in the context window.

The current recommendation is to target under five hundred lines per CLAUDE.md file. This is a best-practice guideline, not a technical limit. If you cannot fit your package's instructions in that range, you are probably including details that do not need to be in the CLAUDE.md at all.

Here is how to prioritise content when approaching the limit.

First priority is build and test commands. These are the instructions Claude Code uses most frequently. Every CLAUDE.md should start with how to build, test, and lint the code.

Second priority is coding standards that Claude frequently gets wrong. If Claude Code consistently uses the wrong import style or the wrong error handling pattern, add a rule for it. If it already follows the convention correctly from reading your code, you do not need a rule.

Third priority is project structure. A brief description of where things live helps Claude Code navigate large packages without reading every directory.

Lowest priority is explanatory text. Claude Code does not need paragraphs explaining why you chose a particular framework. It needs concise instructions about how to use it.

If you genuinely need more context for a particular area, use the @path/to/file syntax to reference external files.

Using Imports to Reference Other Files

Sometimes you need Claude Code to understand detailed specifications, API schemas, or architectural decisions that do not fit neatly in a single CLAUDE.md. The @path/to/file syntax lets you reference other files that Claude Code will load when needed.

# My Package

## Build
- `npm run build`
- `npm test`

## API Schema
@docs/api-schema.md

## Architecture Decisions
@docs/architecture-decisions.md

The @path syntax tells Claude Code to read the referenced file and include its contents in the context. This is more flexible than cramming everything into one file because the imported files can be longer, can be shared across multiple CLAUDE.md files, and can be maintained independently.

Imports work well for three types of content. API documentation that Claude Code needs to generate correct client calls. Architecture decision records that explain why certain patterns are used. And coding standards documents that are maintained by the team and would be duplicated if copied into every CLAUDE.md file.

Be selective with imports. Each imported file adds to the context window size. If you import five large files, you will eat into the context available for your actual conversation with Claude Code.

A Real-World Monorepo Example

Here is the complete CLAUDE.md structure of a real-world monorepo. The repository has this layout.

myproject/
  CLAUDE.md                    # Root, shared conventions
  CLAUDE.local.md              # Personal prefs (gitignored)
  .claude/
    rules/
      typescript-tests.md      # Rules for *.test.ts files
      go-tests.md              # Rules for *_test.go files
      terraform.md             # Rules for *.tf files
  packages/
    web/
      CLAUDE.md                # React frontend instructions
    api/
      CLAUDE.md                # Node.js API instructions
    shared/
      CLAUDE.md                # Shared library instructions
  services/
    auth/
      CLAUDE.md                # Go auth service instructions
    billing/
      CLAUDE.md                # Go billing service instructions
  infra/
    CLAUDE.md                  # Terraform instructions

When a developer opens Claude Code in packages/web/, it loads four sources. The user-level ~/.claude/CLAUDE.md (personal, global), the root CLAUDE.md (shared conventions), the packages/web/CLAUDE.md (React-specific instructions), and any .claude/rules/ files that match the file types being edited.

When the same developer switches to services/auth/, the loaded context changes. The root file stays the same, but packages/web/CLAUDE.md drops out and services/auth/CLAUDE.md loads instead. If they are editing a _test.go file, the go-tests.md rules file also activates.

This is the key benefit of the hierarchy. The context adapts automatically to where the developer is working. No manual switching, no remembering to update settings, no wasted tokens on irrelevant instructions.

Anti-Patterns to Avoid

Teams make the same mistakes repeatedly when setting up CLAUDE.md for monorepos. Here are the patterns to avoid.

Duplicating information across files. If your root CLAUDE.md says "use conventional commits" and every package file also says "use conventional commits," you are wasting context tokens on repetition. Put shared rules in the root file. Put package-specific rules in package files. Never repeat yourself.

Putting everything in the root file. This is the most common mistake. The root file should be a map, not an encyclopedia. If it contains build commands for every package, coding standards for every language, and testing patterns for every framework, it is doing too much. Move package-specific content to package-level files.

Ignoring subdirectory files entirely. Some teams create a single root CLAUDE.md and never add package-level files. This forces the root file to grow indefinitely and means Claude Code gets the same context regardless of where the developer is working. Take advantage of the hierarchy.

Contradicting instructions across levels. If the root file says "always add JSDoc comments" and a package file says "no comments, the code should be self-documenting," Claude Code will receive conflicting instructions. Resolve conflicts by being specific. The root file should set defaults. Package files should override only when necessary, with clear language like "In this package, unlike the general convention..."

Using CLAUDE.md for documentation. CLAUDE.md is for instructions, not documentation. Do not explain the history of your architecture decisions, the rationale behind your tech stack, or the goals of your product. Claude Code does not need this context to write good code. Keep it focused on actionable instructions.

Skills vs CLAUDE.md

Claude Code supports slash commands and skills that provide on-demand context rather than persistent context. Understanding when to use each is important for keeping your CLAUDE.md files lean.

CLAUDE.md is for instructions that should always be active. Coding standards, build commands, project structure. These are things Claude Code needs to know for every interaction within a given scope.

Skills and slash commands are for instructions that are needed occasionally. A complex deployment process, a database migration workflow, a performance profiling procedure. These are detailed instructions that would waste context if loaded into every session but are invaluable when needed.

In a monorepo, use CLAUDE.md for the day-to-day context and skills for specialised workflows. A packages/web/CLAUDE.md tells Claude Code how to build and test the frontend. A /deploy-web skill tells it how to deploy the frontend, including the twelve-step process with environment checks, build verification, CDN invalidation, and smoke tests.

The rule of thumb is simple. If Claude Code needs this information in more than half of the sessions where it is working in a particular scope, put it in CLAUDE.md. If it needs it less frequently, make it a skill or document it in a file that can be imported on demand.

Memory Auto-Save and Project-Specific Memory

Claude Code has a memory feature that automatically saves context to ~/.claude/projects/{project-path}/memory/. This is separate from CLAUDE.md and serves a different purpose.

Memory captures things Claude Code learns during sessions. If you tell it "in this project, we use pnpm, not npm" during a conversation, it may save that to memory so it remembers in future sessions. This is useful for capturing ad-hoc preferences that do not warrant a CLAUDE.md update.

In a monorepo context, memory is project-level, not package-level. This means a correction you make while working in packages/web/ is remembered when you are working in services/auth/. For most corrections, this is fine. But if you make a package-specific correction that should not apply elsewhere, add it to the package-level CLAUDE.md instead of relying on memory.

Periodically reviewing project memory files and promoting useful entries to the appropriate CLAUDE.md file is a good practice. This keeps the important instructions in version control where the whole team benefits from them, rather than locked in a personal memory store.

The Lesson

The most valuable lesson from structuring CLAUDE.md files across a monorepo is that context management is a design problem, not a documentation problem. You are not writing documentation for Claude Code to read. You are designing a context system that delivers the right information at the right time with minimal waste.

The hierarchy, with root files for shared conventions, package files for specific instructions, rules for file-type behaviours, and local files for personal preferences, gives you a framework for thinking about where each piece of context belongs. When you add a new instruction, ask yourself three questions. Does every session need this? Does only this package need this? Does only this file type need this?

The answer tells you where it goes. Keep files short. Be specific. Do not repeat yourself. Let the hierarchy do the work of scoping context to the right sessions. Review your CLAUDE.md files regularly, because your conventions evolve and your context should evolve with them.

Conclusion

Structuring CLAUDE.md for a monorepo is straightforward once you understand the loading hierarchy. Put shared conventions in the root file. Put package-specific instructions in subdirectory files. Use .claude/rules/ for path-scoped file-type rules. Keep personal preferences in CLAUDE.local.md.

Target under five hundred lines per file, and use @path/to/file imports for content that does not fit. The payoff is significant. Claude Code behaves consistently across your entire monorepo, loading only the context relevant to the current task. Developers do not need to manually switch modes or remind Claude Code which package they are working in.

For a deeper understanding of the relationship between CLAUDE.md and system prompts, read the system prompts vs CLAUDE.md guide. And for practical tips on integrating Claude Code into your daily development routine across any project structure, the Claude Code daily workflows guide covers the workflows and habits that make the biggest difference.

The monorepo adds complexity, but with the right CLAUDE.md structure, Claude Code handles that complexity gracefully. Set up the hierarchy once, maintain it as your codebase evolves, and let Claude Code focus on what it does best: writing good code in the right context.