Advanced (Updated: 6/1/2026)

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.

Claude Code Monorepo Management Guide: pnpm, Turborepo, Nx, 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

  1. Shared UI change: ask Claude Code to add a loading state to packages/ui without changing the public API, then list affected screens in apps/web.

  2. Shared DTOs: move safe request and response types into packages/shared, but keep database models and framework-specific code out of it.

  3. Dependency upgrades: update TypeScript, Next.js, or test tooling from packages/config first, 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.

#Claude Code #monorepo #pnpm workspace #Turborepo #Nx
Free

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.

Masa

About the Author

Masa

Engineer focused on practical Claude Code workflows. Runs claudecode-lab.com, a 10-language technical media site.