Tutorials

How to Split Monorepo Rules into Packages

A monorepo with a React frontend, Node.js backend, and shared library needs different AI rules for each package. This tutorial covers the split: root rules for shared conventions, package rules for stack-specific patterns.

5 min read·July 5, 2025

Root CLAUDE.md: shared conventions. apps/web/CLAUDE.md: Next.js patterns. apps/api/CLAUDE.md: NestJS patterns. Each package: the right rules.

Root shared rules, package-specific rules, directory-based AI traversal, documented overrides, and automated conflict validation

One Monorepo, Multiple Tech Stacks, Different Rules

A typical monorepo: apps/web (Next.js frontend), apps/api (NestJS backend), packages/shared (TypeScript utilities), and packages/ui (React component library). Each package: different framework, different patterns, different conventions. A single CLAUDE.md at the root: either too generic (does not cover framework-specific patterns) or too bloated (React rules irrelevant when editing the NestJS backend). The solution: split rules into root (shared) and package-level (specific).

The split model: root CLAUDE.md (conventions shared across all packages — TypeScript strict mode, naming conventions, testing standards, security rules), and per-package CLAUDE.md (conventions specific to that package — apps/web/CLAUDE.md for Next.js patterns, apps/api/CLAUDE.md for NestJS patterns). The AI: reads both the package-level rules AND the root rules. Package rules: override root rules for conflicts.

How AI tools handle monorepo rules: Claude Code reads the CLAUDE.md closest to the file being edited, then looks up the directory tree. If you edit apps/web/src/page.tsx: Claude reads apps/web/CLAUDE.md (package rules) and then the root CLAUDE.md (shared rules). Cursor: similar behavior with .cursorrules. This natural directory traversal: makes the split work without additional configuration.

Step 1: Root-Level Shared Rules

The root CLAUDE.md: conventions that apply to every package in the monorepo. Contents: project overview (monorepo structure, package relationships, build system — Turborepo, Nx, or pnpm workspaces), TypeScript configuration (strict mode, path aliases, shared tsconfig), naming conventions (camelCase for TypeScript, consistent across all packages), security rules (no secrets in code, parameterized queries — applies everywhere), testing standards (Vitest or Jest, co-located test files, naming conventions), and import conventions (workspace package imports use @org/ prefix).

Keep root rules technology-agnostic: the root rules should not mention React, NestJS, or any framework-specific pattern. Those belong in package rules. Root rules: describe what is true for ALL code in the monorepo. If a rule only applies to some packages: it belongs at the package level. AI rule: 'The root CLAUDE.md test: does this rule apply to every package in the monorepo? If yes: root. If no: package-level.'

Workspace-specific rules: monorepos have workspace-specific conventions that single-repo projects do not. 'Import shared packages with the workspace alias (@org/shared, @org/ui — not relative paths). Changes to packages/shared: may affect all consuming packages (run the full test suite, not just the changed package). Package boundaries: do not import from another app's internal modules (apps/web should not import from apps/api/src — use the shared package instead).' These rules: specific to monorepo architecture.

💡 The Root Test: Does This Rule Apply to ALL Packages?

TypeScript strict mode: applies to web, api, shared, and ui. → Root. Server Components by default: applies only to web. → Package. Prisma for database access: applies only to api. → Package. Pure functions for shared code: applies only to shared. → Package. Naming conventions (camelCase): applies to all. → Root. The test: simple, definitive, and prevents root-level rules from containing framework-specific clutter.

Step 2: Package-Level Rules

apps/web/CLAUDE.md: Next.js App Router conventions. 'Server Components by default. Client Components only with "use client". Data fetching in Server Components (no useEffect for data). Tailwind CSS for styling. next/image for all images. Route handlers in app/api/.' These rules: irrelevant to the NestJS backend. They belong in the web app's package, not the root.

apps/api/CLAUDE.md: NestJS conventions. 'Modules for each feature (controller + service + DTOs). Dependency injection via constructor. Decorators for routing (@Get, @Post). Validation pipes on all endpoints. Prisma for database access (the API owns the database layer).' These rules: irrelevant to the React frontend. They belong in the API package.

packages/shared/CLAUDE.md: shared library conventions. 'Pure functions only (no side effects — shared code must be predictable). Explicit TypeScript types (no inference for exported functions — consumers need clear type signatures). No framework dependencies (the shared package must work in both Next.js and NestJS contexts). Comprehensive JSDoc for all exported functions.' These rules: ensure the shared package is truly shared — usable by any consumer. AI rule: 'Each package's CLAUDE.md: contains only the rules specific to that package's technology and purpose. Root rules: inherited automatically.'

⚠️ Undocumented Overrides Look Like Mistakes

Root rule: 'Use Vitest for testing.' The API package: 'Use Jest for testing.' Without documentation: a new developer sees the conflicting rules and thinks: 'Someone forgot to update the API rules when the org switched to Vitest. Let me fix it.' They change the API rules to Vitest. NestJS tests: break (the testing utilities require Jest). With documentation: 'Override: uses Jest instead of Vitest. Reason: @nestjs/testing requires Jest.' The new developer: understands and leaves the override in place.

Step 3: Inheritance and Overrides

Inheritance: the AI reads both root and package rules. Root rules: the baseline. Package rules: additions and overrides. Example: root says 'use Vitest for testing.' The API package says 'use Jest for testing (NestJS testing utilities require Jest).' The package rule: overrides the root for the API package. The web package: inherits the Vitest rule from root (no override needed). The override: explicit and documented in the package CLAUDE.md.

Override documentation: when a package overrides a root rule, document why. 'Override: root rule says Vitest. This package uses Jest. Reason: NestJS @nestjs/testing requires Jest. When NestJS supports Vitest: migrate and remove this override.' The documentation: prevents future developers from thinking the override is accidental. The migration plan: ensures the override is temporary (resolved when the blocker is removed). AI rule: 'Every package override: documented with why and when it will be resolved. Undocumented overrides: look like mistakes.'

Validation: periodically verify that package rules do not contradict root rules unintentionally. A script: compares each package CLAUDE.md against the root CLAUDE.md and flags potential conflicts. Intentional overrides (documented with why): accepted. Unintentional conflicts (the package says camelCase, the root says PascalCase for the same context): flagged for resolution. AI rule: 'Monorepo rule validation: automated. Run after any rule file change. Catches unintentional conflicts between root and package rules.'

ℹ️ The AI's Directory Traversal Makes the Split Work Automatically

Edit apps/web/src/components/Button.tsx. Claude Code: reads apps/web/CLAUDE.md (Next.js rules), then reads the root CLAUDE.md (shared rules). Both sets of rules: applied. The Next.js rules: override shared rules for conflicts. Edit packages/shared/src/utils/format.ts. Claude Code: reads packages/shared/CLAUDE.md (pure function rules), then reads root CLAUDE.md (shared rules). No Next.js rules. No NestJS rules. Just shared and purity rules. The directory structure: naturally scopes the rules. No configuration needed.

Monorepo Rule Split Summary

Summary of splitting monorepo AI rules into packages.

  • Root CLAUDE.md: shared conventions (TypeScript, naming, security, testing, workspace rules)
  • Package CLAUDE.md: stack-specific conventions (Next.js in web, NestJS in api, purity in shared)
  • Root test: does the rule apply to ALL packages? If yes: root. If no: package-level
  • AI traversal: reads package CLAUDE.md first, then root. Package overrides root for conflicts
  • Workspace rules: import via aliases, respect package boundaries, full test suite for shared changes
  • Overrides: documented with why and migration plan. Undocumented overrides look like mistakes
  • Validation: automated script flags unintentional conflicts between root and package rules
  • Result: each package gets exactly the right rules. No irrelevant framework rules cluttering context