Claude Code Monorepo Management Guide: pnpm, Turborepo, Nx, and CI
Manage monorepos with Claude Code using repo maps, pnpm workspaces, affected tasks, CODEOWNERS, and CI.
A monorepo is a setup where multiple apps and libraries are managed in one Git repository. It can make shared UI, types, lint rules, and release checks easier to maintain. It can also become slow and risky when package boundaries are vague, every pull request runs the whole CI suite, and Claude Code is asked to “clean things up” without constraints.
The practical workflow is simple: make Claude Code build a repo map first, define package boundaries, use pnpm workspace and workspace:* dependencies, run only affected tasks with Turborepo or Nx, and keep ownership visible with CODEOWNERS. The official concepts worth keeping nearby are Nx’s monorepo decision guide, Nx affected, the Nx mental model, pnpm, and the Turborepo docs.
The Shape to Aim For
Start with a map before asking Claude Code to edit. Without a map, packages/shared often turns into a dumping ground and every app slowly depends on every other app.
graph TD
WEB["apps/web"] --> UI["packages/ui"]
WEB --> SHARED["packages/shared"]
API["apps/api"] --> SHARED
UI --> CONFIG["packages/config"]
SHARED --> CONFIG
CI["CI affected tasks"] --> WEB
CI --> API
In this model, apps/* are deployable products and packages/* are reusable building blocks. A package boundary is the rule that says which package may depend on which other package. Claude Code should be told to preserve those boundaries, not infer them from the current imports.
First Prompt: Build the Repo Map
Use this prompt before edits:
Read this repository as a monorepo.
Assumptions:
- apps/web is the Next.js app
- apps/api is the API server
- packages/ui contains reusable UI
- packages/shared contains types, validation, and pure utilities
- packages/config contains shared ESLint, TypeScript, Prettier, and test config
Rules:
- apps/* must not depend directly on apps/*
- packages/* must not depend on apps/*
- internal packages must use workspace:* versions
- after edits, lint/test/build should run with affected tasks
First produce a repo map: dependencies, risky cycles, over-shared files, and CI commands to verify.
Do not edit files yet.
That last line matters. Claude Code is good at moving quickly, but monorepo work is safer when the first output is a reviewable model, not a patch.
pnpm Workspace Setup
pnpm-workspace.yaml is the entry point:
packages:
- "apps/*"
- "packages/*"
Put stable scripts at the root so both humans and Claude Code use the same commands.
{
"name": "acme-monorepo",
"private": true,
"packageManager": "pnpm@10.12.1",
"scripts": {
"build": "turbo run build",
"lint": "turbo run lint",
"test": "turbo run test",
"typecheck": "turbo run typecheck",
"ci:affected": "turbo run lint test build --affected",
"check:deps": "node scripts/check-workspace-deps.cjs"
}
}
Internal dependencies should be explicit:
{
"dependencies": {
"@acme/shared": "workspace:*",
"@acme/ui": "workspace:*"
}
}
When asking Claude Code to wire a package, include the dependency policy:
Make apps/web use @acme/ui and @acme/shared.
Use workspace:* in package.json.
Do not import through ../../packages paths.
After the edit, make sure pnpm check:deps and pnpm ci:affected can verify the change.
Turborepo and Nx Affected Tasks
Turborepo is a good default when your packages already expose scripts. Use the current tasks form:
{
"$schema": "https://turbo.build/schema.json",
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**", "!.next/cache/**"]
},
"lint": {
"dependsOn": ["^build"]
},
"test": {
"dependsOn": ["^build"],
"outputs": ["coverage/**"]
},
"typecheck": {
"dependsOn": ["^build"]
},
"dev": {
"cache": false,
"persistent": true
}
}
}
Nx is useful when you want a stronger project graph and more precise affected logic. Add it intentionally:
pnpm dlx nx@latest init
pnpm nx affected -t lint test build --base=origin/main --head=HEAD
For Claude Code, the instruction should be “run the affected checks” rather than “run everything.” That keeps CI costs and feedback time under control.
Ownership and Dependency Policy
CODEOWNERS keeps review responsibility visible:
/apps/web/ @acme/frontend
/apps/api/ @acme/backend
/packages/ui/ @acme/design-system
/packages/shared/ @acme/platform
/packages/config/ @acme/platform
/pnpm-workspace.yaml @acme/platform
/turbo.json @acme/platform
Then enforce the dependency policy with code. Save this as scripts/check-workspace-deps.cjs.
const fs = require("node:fs");
const path = require("node:path");
const ROOT = process.cwd();
const WORKSPACE_DIRS = ["apps", "packages"];
const DEP_FIELDS = [
"dependencies",
"devDependencies",
"peerDependencies",
"optionalDependencies",
];
function readJson(file) {
return JSON.parse(fs.readFileSync(file, "utf8"));
}
function findPackageDirs(baseDir) {
const absoluteBase = path.join(ROOT, baseDir);
if (!fs.existsSync(absoluteBase)) return [];
return fs
.readdirSync(absoluteBase, { withFileTypes: true })
.filter((entry) => entry.isDirectory())
.map((entry) => path.join(absoluteBase, entry.name))
.filter((dir) => fs.existsSync(path.join(dir, "package.json")));
}
const packages = WORKSPACE_DIRS.flatMap(findPackageDirs).map((dir) => {
const manifest = readJson(path.join(dir, "package.json"));
return {
dir,
name: manifest.name,
manifest,
};
});
const byName = new Map(packages.map((pkg) => [pkg.name, pkg]));
let failed = false;
for (const pkg of packages) {
for (const field of DEP_FIELDS) {
const deps = pkg.manifest[field] || {};
for (const [name, range] of Object.entries(deps)) {
const internal = byName.get(name);
if (!internal) continue;
const fromDir = path.relative(ROOT, pkg.dir).replace(/\\/g, "/");
const toDir = path.relative(ROOT, internal.dir).replace(/\\/g, "/");
if (!String(range).startsWith("workspace:")) {
console.error(`${pkg.name}: ${name} must use workspace:* in ${field}`);
failed = true;
}
if (toDir.startsWith("apps/")) {
console.error(`${pkg.name}: ${fromDir} must not depend on app package ${toDir}`);
failed = true;
}
}
}
}
if (failed) process.exit(1);
console.log(`Checked ${packages.length} workspace packages.`);
CI Checklist
This workflow keeps history available for affected task detection and runs the dependency policy before build checks.
name: monorepo-ci
on:
pull_request:
push:
branches: [main]
jobs:
checks:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: pnpm/action-setup@v4
with:
version: 10
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm check:deps
- run: pnpm ci:affected
Three Practical Use Cases
-
Shared UI change: ask Claude Code to add a loading state to
packages/uiwithout changing the public API, then list affected screens inapps/web. -
Shared DTOs: move safe request and response types into
packages/shared, but keep database models and framework-specific code out of it. -
Dependency upgrades: update TypeScript, Next.js, or test tooling from
packages/configfirst, then verify affected apps instead of blindly rebuilding the whole repository.
A fourth common case is feature slicing. Billing, search, or onboarding may touch UI, API, shared schema, and logging. Ask Claude Code to split the work into several small PRs rather than one giant monorepo patch.
Pitfalls to Avoid
The first pitfall is making packages/shared a junk drawer. Shared code should be stable, generic, and easy to test. Do not put API clients, database connections, or screen-specific hooks there.
The second pitfall is crossing boundaries with relative imports such as ../../packages/shared/src. Use the package name and declare the dependency.
The third pitfall is adopting Turborepo and Nx deeply at the same time. Start with one task runner model, then add the other only when the project graph or CI requirements justify it.
The fourth pitfall is trusting “it works locally.” In a monorepo, one app can pass while another app breaks through a shared package. The PR body should name changed packages, affected apps, commands run, and remaining risk.
Review Prompt
Review this diff from a monorepo perspective.
Check:
- no direct apps/* to apps/* dependency
- no packages/* to apps/* dependency
- internal dependencies use workspace:*
- packages/shared contains only stable shared code
- affected lint/test/build coverage is enough
- CODEOWNERS review responsibility is clear
Return:
- blockers
- recommended fixes
- verified commands
- impact summary for the PR body
For deeper setup, continue with Claude Code and Nx workspace, Claude Code and pnpm workspace, Claude Code with Turborepo, and Claude Code team collaboration.
ClaudeCodeLab can help teams turn this into an operating model: CLAUDE.md, package boundaries, CODEOWNERS, CI checks, and review prompts around a real repository. For that level of rollout, use the Claude Code training and consultation page.
Summary
Claude Code works well in monorepos when the constraints are explicit. Give it a repo map, package boundaries, pnpm workspace rules, affected tasks, CODEOWNERS, a dependency policy, and a small CI checklist. The best result is not a clever one-off patch; it is a repeatable workflow that every PR can survive.
After applying this workflow in practice, the biggest wins came from enforcing workspace:* and standardizing pnpm ci:affected. Asking Claude Code to list affected apps whenever packages/shared changed reduced review misses and made CI costs easier to predict.
Free PDF: Claude Code Cheatsheet
Enter your email and download the one-page Claude Code cheatsheet for commands, review habits, and safe workflows.
We handle your data with care and never send spam.
Level up your Claude Code workflow
Start with the free PDF, use Gumroad guides when you need repeatable workflows, and book consultation when rollout or revenue paths need human judgment.
About the Author
Masa
Engineer focused on practical Claude Code workflows. Runs claudecode-lab.com, a 10-language technical media site.
Related Posts
Claude Code Permission Receipt Pattern: Record Scope, Proof, and Rollback
A permission receipt pattern for Claude Code: allowed actions, approval boundaries, proof commands, rollback, and revenue CTA checks.
Safe Agent Harness Design for Claude Code and Codex: Permissions, Checks, and Rollback
Build a practical agent harness for Claude Code and Codex with policy, planning, verification, and recovery layers.
Claude Code Subagents: A Practical Guide to Safe Agent Delegation
Claude Code subagent guide for safe parallel article and code work: delegation rules, prompts, pitfalls, and checks.
Related Products
50 Battle-Tested Claude Code Prompt Templates
Copy, paste, ship. 50 production-ready prompts.
Use proven prompts for code review, refactoring, testing, documentation, debugging, architecture, and incident response.
The Complete Claude Code Setup & Configuration Guide
From install to team-ready workflow.
A practical guide to installation, CLAUDE.md, hooks, MCP servers, permissions, IDE setup, and CI/CD workflows.