Monorepos mit Claude Code verwalten: pnpm, Turborepo, Nx und CI
Sichere Monorepo-Arbeit mit Claude Code: Repo-Map, pnpm workspace, affected tasks, CODEOWNERS und CI.
Ein Monorepo ist ein Git-Repository, in dem mehrere Apps und Bibliotheken gemeinsam verwaltet werden. Das ist praktisch, wenn apps/web, apps/api, packages/ui und packages/shared dieselben Typen, UI-Bausteine und CI-Regeln nutzen. Ohne klare Grenzen kann Claude Code aber zu viel auf einmal ändern: ein scheinbar kleiner Fix in einem Shared-Paket bricht dann mehrere Apps.
Der robuste Ablauf lautet: zuerst eine Repo-Map erstellen lassen, dann Package Boundaries definieren, interne Abhängigkeiten mit pnpm workspace und workspace:* fixieren, anschließend nur betroffene Tasks mit Turborepo oder Nx ausführen. Offizielle Grundlagen findest du bei Nx Why Monorepos, Nx affected, Nx mental model, pnpm und Turborepo.
Zielbild
Lass Claude Code zuerst die Struktur verstehen, bevor Dateien editiert werden.
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
apps/* sind deploybare Produkte. packages/* sind wiederverwendbare Bausteine. Eine Package Boundary legt fest, welche Pakete voneinander abhängen dürfen. Claude Code sollte diese Regeln nicht erraten, sondern explizit bekommen.
Erster Claude-Code-Prompt
Analysiere dieses Repository als Monorepo.
Annahmen:
- apps/web ist die Next.js App
- apps/api ist der API-Server
- packages/ui enthält wiederverwendbare UI
- packages/shared enthält Typen, Validierung und reine Hilfsfunktionen
- packages/config enthält ESLint, TypeScript, Prettier und Test-Konfiguration
Regeln:
- apps/* darf nicht direkt von apps/* abhängen
- packages/* darf nicht von apps/* abhängen
- interne Pakete verwenden workspace:* Versionen
- nach Änderungen laufen lint/test/build als affected tasks
Erstelle zuerst eine Repo-Map: Abhängigkeiten, riskante Zyklen, übermäßig geteilte Dateien und CI-Kommandos.
Noch keine Dateien ändern.
Dieser Prompt hält die erste Antwort reviewbar. Bei Monorepos ist das Problem oft nicht Syntax, sondern die Richtung der Abhängigkeiten.
pnpm Workspace
packages:
- "apps/*"
- "packages/*"
Im Root-package.json sollten stabile Befehle liegen.
{
"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"
}
}
Interne Pakete werden so referenziert:
{
"dependencies": {
"@acme/shared": "workspace:*",
"@acme/ui": "workspace:*"
}
}
Eine gute Aufgabe für Claude Code ist konkret:
Mache @acme/ui und @acme/shared in apps/web nutzbar.
Verwende workspace:* in package.json.
Keine Imports über ../../packages.
Nach der Änderung sollen pnpm check:deps und pnpm ci:affected zur Prüfung reichen.
Turborepo oder Nx affected
Turborepo passt gut, wenn die Pakete bereits Scripts besitzen.
{
"$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 ist stärker, wenn Projektgraph und präzise Einflussanalyse wichtiger werden.
pnpm dlx nx@latest init
pnpm nx affected -t lint test build --base=origin/main --head=HEAD
Die Anweisung an Claude Code sollte nicht “alles bauen” lauten. Besser ist: “Führe die affected lint/test/build Checks für diese Änderung aus.”
CODEOWNERS und Dependency Policy
/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
Zusätzlich sollte die Dependency Policy maschinell geprüft werden. Lege diese Datei als scripts/check-workspace-deps.cjs ab.
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-Checkliste
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
fetch-depth: 0 ist wichtig, weil affected Checks die Git-Historie brauchen.
Drei Praxisfälle
-
Ein Button in
packages/uierhält einen Loading State. Claude Code soll die öffentliche API stabil halten und betroffene Screens inapps/webnennen. -
Gemeinsame DTOs wandern nach
packages/shared. DTOs sind Datenformen für API und UI, nicht Datenbankmodelle. -
TypeScript, Next.js oder Test-Tools werden aktualisiert. Starte bei
packages/configund prüfe danach nur die betroffenen Apps.
Ein vierter Fall sind funktionsübergreifende Features wie Billing oder Suche. Claude Code sollte dafür kleine PRs vorschlagen, statt UI, API, Schema und Logging in einen riesigen Patch zu packen.
Typische Fehler
Erstens: packages/shared wird zur Ablage für alles. Dort gehören nur stabile, generische, gut testbare Teile hinein.
Zweitens: relative Imports wie ../../packages/shared/src. Sie umgehen die Paketgrenze und machen Builds schwerer nachvollziehbar.
Drittens: Turborepo und Nx gleichzeitig tief einführen. Entscheide dich zuerst für ein Hauptmodell.
Viertens: “Läuft lokal” als Beweis akzeptieren. Im Monorepo muss der PR-Text geänderte Pakete, betroffene Apps, ausgeführte Kommandos und Restrisiken nennen.
Review-Prompt
Reviewe diesen Diff aus Monorepo-Sicht.
Prüfe:
- keine direkte apps/* zu apps/* Abhängigkeit
- keine packages/* zu apps/* Abhängigkeit
- interne Dependencies verwenden workspace:*
- packages/shared enthält nur stabile Shared-Logik
- affected lint/test/build deckt die Änderung ab
- CODEOWNERS macht die Review-Verantwortung klar
Ausgabe:
- Blocker
- empfohlene Korrekturen
- geprüfte Kommandos
- Impact Summary für den PR-Text
Für mehr Kontext: Claude Code und Nx workspace, Claude Code und pnpm workspace, Claude Code mit Turborepo, Claude Code Team Collaboration.
Wenn dein Team Claude Code in ein bestehendes Monorepo einführt, sind Grenzen, Owners, CI und Review-Prompts wichtiger als die Tool-Diskussion. ClaudeCodeLab unterstützt dabei über Claude Code Training und Beratung mit CLAUDE.md, CODEOWNERS, CI und PR-Workflow am realen Repository.
Fazit
Claude Code funktioniert im Monorepo gut, wenn die Leitplanken klar sind: Repo-Map, Package Boundaries, pnpm workspace, Turborepo/Nx affected tasks, CODEOWNERS, Dependency Policy und CI-Checkliste. In der Praxis bringen besonders workspace:* und ein standardisiertes pnpm ci:affected sofort weniger Review-Lücken und kürzere CI-Läufe.
Kostenloses PDF: Claude-Code-Cheatsheet
E-Mail eintragen und eine Seite mit Befehlen, Review-Gewohnheiten und sicheren Workflows herunterladen.
Wir schützen Ihre Daten und senden keinen Spam.
Über den Autor
Masa
Engineer für praktische Claude-Code-Workflows und Team-Einführung.
Ähnliche Artikel
Claude-Code-Permission-Receipt: Scope, Beweis und Rollback festhalten
Permission-Receipt für Claude Code: erlaubte Aktionen, Freigabegrenzen, Prüfbefehle, Rollback und Umsatz-CTA-Prüfung.
Sicheres Agent Harness fur Claude Code und Codex: Rechte, Prufung und Rollback
Ein praktisches Agent Harness fur Claude Code und Codex mit Policy, Plan, Verifikation und Recovery.
Claude Code Subagents: Praxisleitfaden für sichere Agent-Delegation
Claude Code Subagents praktisch nutzen: Artikel- und Codearbeit sicher aufteilen, Prompts einsetzen, Fehler vermeiden.