Monorepo & Tooling

pnpm Workspaces mit Claude Code: Monorepo-Setup 2026

pnpm Workspaces sind der schnellste Weg zum Monorepo: geteilte Packages, lokale Symlinks, drastisch weniger Duplikate im node_modules. Claude Code kennt das gesamte Setup — von der ersten pnpm-workspace.yaml bis zu Turborepo-Pipeline und Changesets-Release-Workflow.

5. Mai 2026 10 min Lesezeit

1. Monorepo vs. Multi-Repo: Wann lohnt sich ein Monorepo?

Die Entscheidung zwischen Monorepo und Multi-Repo ist keine Glaubensfrage — sie haengt vom Team, der Code-Struktur und den Abhaengigkeiten ab. Monorepos gewinnen immer dann, wenn mehrere Apps denselben Code teilen, Teams eng zusammenarbeiten und atomare Commits ueber Projektgrenzen wichtig sind.

KriteriumMonorepoMulti-Repo
Shared Code zwischen AppsDirekte Symlinks, kein publishnpm publish + Versionierung
Atomic CommitsApp + Package in 1 CommitKoordinierter PR in 2+ Repos
CI-Laufzeit bei AenderungNur betroffene Packages (mit Turbo)Nur das geaenderte Repo
Onboarding neuer Devs1 Clone, 1 InstallN Repos klonen, N Setups
Unabhaengige ReleasesMoeglich (Changesets)Nativ, kein Extra-Tool
Disk-Space (node_modules)pnpm dedupliziert globalPro Repo eigene Kopien
Skalierung (100+ Packages)Turborepo/Nx noetigEinfacher isoliert

pnpm vs. npm vs. Yarn Workspaces

VergleichWarum pnpm fuer Monorepos?

Claude Code Prompt-Tipp: "Analysiere unser bestehendes Multi-Repo Setup (frontend-app, backend-api, shared-utils) und erstelle einen Migrationsplan zu einem pnpm Workspace Monorepo mit minimaler Downtime."

2. pnpm Workspace Setup: Von Null zum Monorepo

Der Einstieg in pnpm Workspaces ist ueberraschend schnell. Die zentrale Konfigurationsdatei pnpm-workspace.yaml definiert welche Verzeichnisse als Packages gelten.

SetupMonorepo-Struktur mit pnpm Workspaces

Claude Code Prompt: "Erstelle ein pnpm Workspace Monorepo mit einer Next.js-App, einer Express-API und zwei Shared-Packages (UI, Utils)."

Terminal Init
# pnpm global installieren (falls nicht vorhanden) npm install -g pnpm # Monorepo-Root initialisieren mkdir my-monorepo && cd my-monorepo pnpm init # Verzeichnisstruktur anlegen mkdir -p apps/web apps/api packages/ui packages/utils packages/config tools/scripts
pnpm-workspace.yaml Workspace-Config
# Definiert welche Verzeichnisse Workspace-Packages sind packages: # Alle direkten Unterordner von apps/ - 'apps/*' # Alle direkten Unterordner von packages/ - 'packages/*' # Spezifisch einbinden (alternativ zu Glob) - 'tools/scripts' # Ausschliessen (z.B. archived Packages) - '!**/test/**'
package.json (Root) Root Config
{ "name": "my-monorepo", "version": "0.0.0", "private": true, "engines": { "node": ">=20.0.0", "pnpm": ">=9.0.0" }, "scripts": { "dev": "turbo run dev", "build": "turbo run build", "test": "turbo run test", "lint": "turbo run lint", "clean": "turbo run clean", "format": "prettier --write .", // Changesets Release-Flow "changeset": "changeset", "version-packages": "changeset version", "release": "turbo run build --filter=...[] && changeset publish" }, "devDependencies": { "turbo": "^2.3.0", "@changesets/cli": "^2.27.0", "typescript": "^5.5.0", "prettier": "^3.3.0" } }

Vollstaendige Verzeichnisstruktur

my-monorepo/ ├── apps/ │ ├── web/ # Next.js Frontend │ │ ├── package.json │ │ └── src/ │ └── api/ # Express.js Backend │ ├── package.json │ └── src/ ├── packages/ │ ├── ui/ # React Komponenten Library │ │ ├── package.json │ │ └── src/ │ ├── utils/ # Shared Utility-Funktionen │ │ ├── package.json │ │ └── src/ │ └── config/ # Shared Konfiguration (ESLint, TS, etc.) │ ├── eslint-config/ │ └── typescript-config/ ├── tools/ │ └── scripts/ # Build/Deploy Skripte ├── pnpm-workspace.yaml ├── package.json # Root package.json ├── turbo.json ├── .changeset/ └── tsconfig.base.json # Basis-TypeScript-Config
pnpm install Verhalten: Ein einziges pnpm install im Root installiert Abhaengigkeiten aller Workspace-Packages. pnpm erstellt automatisch Symlinks im node_modules/.pnpm-Store und verlinkt lokale Packages direkt — kein manuelles npm link noetig.

3. Shared Packages: UI-Library, Utils, Config teilen

Das Herzstueck eines Monorepos sind die Shared Packages. Mit dem workspace:-Protokoll referenzieren Apps lokale Packages direkt — Aenderungen sind sofort sichtbar, kein Publish-Zyklus noetig.

packages/ui/package.json UI Package
{ "name": "@my-monorepo/ui", "version": "1.0.0", "main": "./src/index.ts", "exports": { ".": { "import": "./src/index.ts", "types": "./src/index.ts" } }, "scripts": { "build": "tsc --build", "dev": "tsc --build --watch", "lint": "eslint src/", "test": "vitest run", "clean": "rm -rf dist .tsbuildinfo" }, "peerDependencies": { "react": "^18 || ^19", "react-dom": "^18 || ^19" }, "devDependencies": { "@my-monorepo/typescript-config": "workspace:*", "@my-monorepo/eslint-config": "workspace:*", "typescript": "^5.5.0", "vitest": "^2.0.0" } }
apps/web/package.json App Package
{ "name": "@my-monorepo/web", "version": "0.0.0", "private": true, "scripts": { "dev": "next dev", "build": "next build", "start": "next start", "lint": "next lint", "test": "vitest run" }, "dependencies": { // Lokale Packages mit workspace: Protokoll "@my-monorepo/ui": "workspace:*", "@my-monorepo/utils": "workspace:*", "next": "^15.0.0", "react": "^19.0.0", "react-dom": "^19.0.0" }, "devDependencies": { "@my-monorepo/typescript-config": "workspace:*", "@my-monorepo/eslint-config": "workspace:*" } }
packages/ui/src/index.ts UI Exports
// Zentrale Export-Datei der UI Library export { Button } from './components/Button'; export { Card } from './components/Card'; export { Input } from './components/Input'; export { Modal } from './components/Modal'; export * from './types'; export * from './hooks'; // Nutzung in apps/web: // import { Button, Card } from '@my-monorepo/ui';
packages/config/typescript-config/base.json Shared TS Config
{ "$schema": "https://json.schemastore.org/tsconfig", "compilerOptions": { "target": "ES2022", "lib": ["ES2022"], "module": "NodeNext", "moduleResolution": "NodeNext", "strict": true, "noUncheckedIndexedAccess": true, "noImplicitReturns": true, "skipLibCheck": true, "declaration": true, "declarationMap": true, "sourceMap": true } } // packages/ui/tsconfig.json erweitert die Basis: { "extends": "@my-monorepo/typescript-config/base.json", "compilerOptions": { "jsx": "react-jsx", "outDir": "./dist" }, "include": ["src"] }

Workspace-Commands: Filter und Scope

# Nur eine bestimmte App starten pnpm --filter @my-monorepo/web dev # Alle Packages bauen pnpm --filter "./packages/*" build # Rekursiv: Package + alle Abhaengigkeiten pnpm --filter @my-monorepo/web... build # Dependency hinzufuegen (nur in web) pnpm --filter @my-monorepo/web add axios # Dev-Dependency fuer alle Packages pnpm add -D vitest -w # Alle installieren pnpm install # Workspace-Package hinzufuegen pnpm --filter @my-monorepo/web add @my-monorepo/ui --workspace
Phantom Dependencies vermeiden: pnpm blockiert den Zugriff auf nicht-deklarierte Packages. Wenn ein Package X braucht, das eine Abhaengigkeit von Y mitbringt, muss X explizit in package.json deklariert sein. Das ist ein Feature, kein Bug — es verhindert versteckte Abhaengigkeiten die beim Upgrade brechen.

4. Turborepo fuer Build-Caching

Turborepo ist der Build-Orchestrator fuer Monorepos. Es analysiert den Abhaengigkeitsgraph und baut nur was sich geaendert hat — lokal per Cache und optional remote ueber Vercel oder einen eigenen Server.

turbo.json Turborepo Config
{ "$schema": "https://turbo.build/schema.json", "ui": "tui", "tasks": { // build: haengt von Build aller Abhaengigkeiten ab "build": { "dependsOn": ["^build"], "inputs": ["$TURBO_DEFAULT$", ".env*"], "outputs": ["dist/**", ".next/**", "!.next/cache/**"] }, // test: haengt von Build ab, aber nicht von test der Deps "test": { "dependsOn": ["^build"], "outputs": ["coverage/**"], "inputs": ["src/**", "tests/**", "vitest.config.ts"] }, // lint: parallel, keine Abhaengigkeiten "lint": { "dependsOn": [], "outputs": [] }, // dev: laeuft dauerhaft, kein Cache "dev": { "cache": false, "persistent": true }, // clean: kein Cache, outputs loeschen "clean": { "cache": false }, // type-check: schnelle TS-Validierung "type-check": { "dependsOn": ["^build"], "outputs": [] } }, // Remote Cache (Vercel) "remoteCache": { "enabled": true, "signature": true } }

Turborepo Remote Caching einrichten

# Vercel Remote Cache (kostenlos fuer Hobby) npx turbo login npx turbo link # Self-hosted: Ducktape (Open Source Remote Cache) # docker run -p 8080:8080 ducktape/turbo-remote-cache # In turbo.json fuer Self-hosted: # "remoteCache": { "apiUrl": "http://localhost:8080" } # Cache-Hit testen: pnpm build pnpm build # Zweiter Run: alle Tasks = "FULL TURBO" (Cache-Hit) # Nur betroffene Packages bauen (nach Aenderung): turbo build --filter=...[HEAD^1] # alles seit letztem Commit turbo build --filter=...[main] # alles gegenueber main Branch

TurboPipeline-Logik verstehen

^build bedeutet: "Warte bis alle Abhaengigkeiten ihren build-Task abgeschlossen haben." So wird sichergestellt, dass packages/ui gebaut ist bevor apps/web startet.

persistent: true kennzeichnet langlebige Prozesse (Watch-Mode, Dev-Server) — Turborepo weiss, dass diese nie "fertig" werden.

inputs/outputs bestimmen Cache-Invalidierung: Wenn keine Datei in inputs sich geaendert hat und outputs gecacht sind, wird der Task uebersprungen.

Claude Code + Turborepo: "Analysiere unsere turbo.json und erklaere warum der build-Task von apps/web immer neu ausgefuehrt wird, obwohl sich nur die API geaendert hat." Claude Code kann den Abhaengigkeitsgraphen analysieren und Konfigurationsfehler in Pipeline-Definitionen finden.

5. Changesets fuer Versionierung

In einem Monorepo mit mehreren veroeffentlichten Packages braucht man ein System fuer semantische Versionierung und CHANGELOG-Generierung. @changesets/cli ist der De-facto-Standard dafuer — es arbeitet mit einem "Changeset"-Konzept, bei dem Entwickler ihre Aenderungen beschreiben bevor sie mergen.

# Changesets installieren pnpm add -D -w @changesets/cli # Changesets initialisieren (erstellt .changeset/config.json) pnpm changeset init
.changeset/config.json Changesets Config
{ "$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json", "changelog": "@changesets/cli/changelog", "commit": false, "fixed": [], "linked": [], "access": "restricted", // "public" fuer NPM-Publizierung "baseBranch": "main", "updateInternalDependencies": "patch", "ignore": [ "@my-monorepo/web", // Apps nicht veroeffentlichen "@my-monorepo/api" ] }

Der Changeset-Workflow

## Schritt 1: Aenderung machen + Changeset erstellen # (interaktiver Dialog) pnpm changeset # Fragt: welche Packages? minor/major/patch? Beschreibung? # Erstellt: .changeset/grumpy-wolves-dance.md ## Schritt 2: Changeset sieht so aus: --- "@my-monorepo/ui": minor --- Added `Tooltip` component with keyboard navigation support ## Schritt 3: Versions-Update (in CI oder lokal) pnpm changeset version # Bumpt Versionen in package.json, erstellt CHANGELOG.md ## Schritt 4: Veroeffentlichen pnpm changeset publish # Publiziert alle geaenderten Packages zu npm ## Schritt 5: Git-Tags erstellen git push --follow-tags
GitHub Actions: Automated Release Release CI
name: Release on: push: branches: [main] jobs: release: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 # benoetigt fuer Changeset-Diff - uses: pnpm/action-setup@v4 with: version: 9 - uses: actions/setup-node@v4 with: node-version: "20" cache: pnpm - name: Install dependencies run: pnpm install --frozen-lockfile - name: Create Release PR or Publish uses: changesets/action@v1 with: publish: pnpm release version: pnpm version-packages title: "chore: version packages" commit: "chore: version packages" env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }}

ChangesetDer automatisierte Release-Loop

Die changesets/action prueft bei jedem Push auf main:

Kein manuelles npm publish mehr — der Workflow ist vollstaendig automatisiert.

6. CI: Nur betroffene Packages testen

Das groesste Performance-Problem in Monorepo-CIs: alles testen obwohl nur ein Package geaendert wurde. Mit Turborepo und pnpm Filter laesst sich das elegant loesen — entweder via Remote Cache (bevorzugt) oder via explizites Affected-Testing.

.github/workflows/ci.yml CI Pipeline
name: CI on: push: branches: [main] pull_request: branches: [main] env: TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} TURBO_TEAM: ${{ vars.TURBO_TEAM }} jobs: build-test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 2 # Turbo braucht HEAD und HEAD^1 - uses: pnpm/action-setup@v4 with: version: 9 - uses: actions/setup-node@v4 with: node-version: "20" cache: pnpm - name: Install run: pnpm install --frozen-lockfile - name: Lint (parallel, kein Build noetig) run: pnpm turbo run lint - name: Type-Check run: pnpm turbo run type-check - name: Build run: pnpm turbo run build - name: Test run: pnpm turbo run test -- --coverage - name: Upload coverage uses: codecov/codecov-action@v4 with: flags: monorepo

Matrix-Strategy: Packages parallel testen

.github/workflows/matrix-ci.yml Matrix Build
name: Matrix CI on: [push, pull_request] jobs: detect-changes: runs-on: ubuntu-latest outputs: packages: ${{ steps.changes.outputs.packages }} steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - uses: pnpm/action-setup@v4 with: { version: 9 } - name: Install run: pnpm install --frozen-lockfile - name: Detect changed packages id: changes run: | PACKAGES=$(pnpm turbo run build --dry=json \ --filter=...[origin/main] \ | jq -c '[.tasks[].package | select(. != null)] | unique') echo "packages=$PACKAGES" >> $GITHUB_OUTPUT test-packages: needs: detect-changes if: ${{ needs.detect-changes.outputs.packages != '[]' }} runs-on: ubuntu-latest strategy: matrix: package: ${{ fromJson(needs.detect-changes.outputs.packages) }} steps: - uses: actions/checkout@v4 - uses: pnpm/action-setup@v4 with: { version: 9 } - name: Install run: pnpm install --frozen-lockfile - name: Test ${{ matrix.package }} run: pnpm turbo run test --filter=${{ matrix.package }}

pnpm Filter-Cheatsheet fuer CI

# Nur Packages die sich gegenueber main geaendert haben pnpm --filter ...[origin/main] test # Package + alle die davon abhaengen (Downstream) pnpm --filter ...@my-monorepo/utils test # Package + alle seine Abhaengigkeiten (Upstream) pnpm --filter @my-monorepo/web... build # Nur Packages in apps/ pnpm --filter "./apps/*" dev # Alle ausser Test-Pakete pnpm --filter "!./tests/*" build # Spezifische Packages pnpm --filter @my-monorepo/ui --filter @my-monorepo/utils test

CIRemote Cache: Die beste Affected-Strategy

Statt manuell betroffene Packages zu ermitteln, ist Turborepo Remote Cache eleganter:

Mit Remote Cache ist die Monorepo-CI oft schneller als Microservice-CI — weil nur der betroffene Teil neu gebaut wird.

Claude Code fuer CI-Optimierung: "Unsere GitHub Actions dauern 12 Minuten fuer das Monorepo. Analysiere die turbo.json, die Workflow-Datei und schlage konkrete Optimierungen vor um unter 3 Minuten zu kommen." Claude Code kann Abhaengigkeitsgraphen analysieren, parallelisierbare Tasks identifizieren und Cache-Konfigurationen optimieren.

Fazit: pnpm Workspaces + Turborepo + Changesets

Das Trio aus pnpm Workspaces, Turborepo und Changesets ist 2026 der solideste Stack fuer JavaScript/TypeScript-Monorepos:

EmpfehlungWann Monorepo, wann nicht?

Monorepo mit pnpm empfohlen: Mehrere Apps teilen Code, Full-Stack-Projekte (Frontend + Backend + Shared Types), interne Component Libraries, Startups die schnell iterieren.

Lieber Multi-Repo: Komplett unabhaengige Teams, stark unterschiedliche Release-Zyklen, Legacy-Systeme mit inkompatiblen Tool-Chains, sehr grosse Projekte (1000+ Packages) wo Nx benoetigt wuerde.

Migration: Claude Code kann bestehende Multi-Repos analysieren und schrittweise Migrationplaene erstellen — Package by Package, ohne Big-Bang-Umstellung.

Monorepo-Entwicklung mit KI-Unterstützung starten

Claude Code kennt pnpm Workspaces, Turborepo und Changesets. Beschreibe dein Projekt — und erhalte ein vollstaendiges Monorepo-Setup in Minuten statt Stunden.

14 Tage kostenlos testen →
Alle Artikel im Blog