Wer mehrere TypeScript-Projekte in einem Repository verwaltet, kennt das Problem: Abhängigkeiten wachsen unkontrolliert, CI-Zeiten explodieren, und der Überblick über den Projekt-Graph geht verloren. Nx adressiert genau diese Probleme — und Claude Code macht den Einstieg und den Alltag mit Nx deutlich produktiver. In diesem Artikel zeige ich, wie du 2026 einen professionellen Nx-Workspace aufbaust und Claude Code als intelligenten Assistenten für Workspace-Entscheidungen nutzt.

Voraussetzungen: Node.js 20+, npm 10+, grundlegende TypeScript-Kenntnisse. Nx-Vorkenntnisse sind hilfreich, aber nicht zwingend erforderlich.

1. Nx Setup & Workspace-Struktur

Der Einstieg in Nx beginnt mit dem create-nx-workspace-Befehl. Er generiert eine vollständige Monorepo-Struktur mit sinnvollen Defaults — Apps, Libs, Konfigurationsdateien und eine aufgeräumte Ordnerstruktur.

Workspace anlegen

# Neuen Nx-Workspace erstellen npx create-nx-workspace@latest my-workspace \ --preset=ts \ --nxCloud=yes \ --packageManager=npm # In den Workspace wechseln cd my-workspace # Typische Ordnerstruktur nach dem Setup my-workspace/ ├── apps/ │ ├── web-app/ │ └── api/ ├── libs/ │ ├── shared/ │ │ ├── ui/ │ │ └── data-access/ │ └── feature/ │ └── auth/ ├── tools/ │ └── generators/ ├── nx.json ├── tsconfig.base.json └── package.json
Struktur

apps/ vs. libs/ — die goldene Regel

apps/ enthält deploybare Applikationen (Frontend, Backend, Microservices). libs/ enthält wiederverwendbare Bibliotheken (UI-Komponenten, Business-Logik, Utilities). Nur apps/ wird deployed — libs/ wird in apps/ importiert.

project.json — die Nx-Projektkonfiguration

Jedes Projekt in einem Nx-Workspace hat eine project.json, die Targets (Befehle), Tags und Abhängigkeiten definiert:

// apps/web-app/project.json { "name": "web-app", "projectType": "application", "tags": ["scope:web", "type:app"], "targets": { "build": { "executor": "@nx/vite:build", "outputs": ["{options.outputPath}"], "options": { "outputPath": "dist/apps/web-app", "tsConfig": "apps/web-app/tsconfig.app.json", "main": "apps/web-app/src/main.ts" } }, "test": { "executor": "@nx/vitest:vitest", "options": { "passWithNoTests": true } }, "lint": { "executor": "@nx/eslint:lint", "options": { "lintFilePatterns": ["apps/web-app/**/*.ts"] } }, "serve": { "executor": "@nx/vite:dev-server", "defaultConfiguration": "development", "options": { "buildTarget": "web-app:build" } } } }

nx.json — globale Workspace-Konfiguration

// nx.json { "$schema": "./node_modules/nx/schemas/nx-schema.json", "defaultBase": "main", "namedInputs": { "default": ["{projectRoot}/**/*", "sharedGlobals"], "production": [ "default", "!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)" ], "sharedGlobals": [] }, "targetDefaults": { "build": { "cache": true, "dependsOn": ["^build"], "inputs": ["production", "^production"] }, "test": { "cache": true, "inputs": ["default", "^production"] }, "lint": { "cache": true, "inputs": ["default", "{workspaceRoot}/.eslintrc.json"] } }, "generators": { "@nx/react": { "library": { "unitTestRunner": "vitest" }, "component": { "style": "css" } } } }
Claude Code Tipp: Frag Claude Code: "Analysiere meine nx.json und erkläre, welche targetDefaults sich gegenseitig beeinflussen." Claude versteht die Abhängigkeitsketten und zeigt Optimierungspotenzial in der Cache-Konfiguration.

tsconfig.base.json — Pfad-Aliase für alle Libs

// tsconfig.base.json — zentrale TypeScript-Konfiguration { "compilerOptions": { "rootDir": ".", "sourceMap": true, "declaration": false, "moduleResolution": "node", "emitDecoratorMetadata": true, "experimentalDecorators": true, "importHelpers": true, "target": "ES2022", "module": "ESNext", "lib": ["ES2022", "DOM"], "skipLibCheck": true, "strict": true, "paths": { "@my-workspace/shared/ui": ["libs/shared/ui/src/index.ts"], "@my-workspace/shared/data-access": ["libs/shared/data-access/src/index.ts"], "@my-workspace/feature/auth": ["libs/feature/auth/src/index.ts"] } } }

2. Projekt-Graph & Dependency Management

Der Nx Projekt-Graph ist das Herzstück des Monorepos. Er analysiert automatisch alle Import-Beziehungen zwischen Projekten und macht sie sichtbar — als interaktive Visualisierung und als Basis für Affected-Entscheidungen.

Den Projekt-Graph visualisieren

# Interaktiven Projekt-Graph im Browser öffnen npx nx graph # Graph für ein spezifisches Projekt anzeigen npx nx graph --focus=web-app # Graph als JSON exportieren (für CI-Analyse) npx nx graph --file=project-graph.json # Alle Projekte die von einer Lib abhängen npx nx graph --affected --base=main # Beispiel-Output: welche Apps hängen von shared/ui ab? Project: @my-workspace/shared/ui Dependents: web-app, dashboard-app, admin-panel
Graph

Implizite vs. explizite Abhängigkeiten

Nx erkennt Abhängigkeiten automatisch durch Import-Analyse. Zusätzlich können implizite Abhängigkeiten in project.json definiert werden:

// project.json — implizite Abhängigkeit { "implicitDependencies": [ "shared-config", "!excluded-lib" // Ausnahme mit ! ] }

Tags & enforce-module-boundaries

Mit Tags in project.json und der enforce-module-boundaries-ESLint-Regel lassen sich Architekturvorgaben maschinell durchsetzen:

// libs/shared/ui/project.json { "tags": ["scope:shared", "type:ui"] } // apps/web-app/project.json { "tags": ["scope:web", "type:app"] } // libs/feature/auth/project.json { "tags": ["scope:web", "type:feature"] }
// .eslintrc.json — Architektur-Constraints erzwingen { "rules": { "@nx/enforce-module-boundaries": [ "error", { "enforceBuildableLibDependency": true, "allow": [], "depConstraints": [ { "sourceTag": "type:app", "onlyDependOnLibsWithTags": [ "type:feature", "type:ui", "type:data-access", "type:util" ] }, { "sourceTag": "type:feature", "onlyDependOnLibsWithTags": [ "type:ui", "type:data-access", "type:util" ] }, { "sourceTag": "type:ui", "onlyDependOnLibsWithTags": ["type:util"] }, { "sourceTag": "scope:web", "notDependOnLibsWithTags": ["scope:admin"] } ] } ] } }

Lint-Fehler durch Grenzüberschreitung

# Alle Boundary-Verletzungen prüfen npx nx run-many -t lint --all # Beispiel-Fehler wenn ui-lib eine feature-lib importiert: Error: libs/shared/ui/src/button.tsx A project tagged with "type:ui" can only depend on libs tagged with "type:util" Import: @my-workspace/feature/auth # VERBOTEN!
Claude Code Workflow: Wenn du einen neuen Import hinzufügen willst, frag Claude: "Darf die lib 'feature/auth' auf 'shared/data-access' zugreifen? Prüfe meine enforce-module-boundaries-Konfiguration." Claude liest die ESLint-Config und beantwortet die Frage mit Begründung.
Tag-Typ Darf importieren Darf NICHT importieren
type:app feature, ui, data-access, util andere apps
type:feature ui, data-access, util app, andere features
type:ui util feature, data-access
type:data-access util ui, feature
type:util nur externe Pakete alle internen libs

3. Affected Commands & Smart CI

Das Killer-Feature von Nx: affected-Commands. Statt alle Projekte zu bauen und zu testen, analysiert Nx den Projekt-Graph und führt Tasks nur für Projekte aus, die von den Änderungen betroffen sind.

Wie affected funktioniert

# Welche Projekte sind von Änderungen betroffen? npx nx show projects --affected --base=main --head=HEAD # Output: nur geänderte Projekte und ihre Dependents shared-ui # direkt geändert web-app # importiert shared-ui → auch affected dashboard-app # importiert shared-ui → auch affected # admin-app: NICHT affected (importiert shared-ui nicht) # Nur affected Projekte testen npx nx affected -t test --base=main --head=HEAD # Nur affected Projekte bauen npx nx affected -t build --base=main --head=HEAD # Lint nur für affected Projekte npx nx affected -t lint --base=main --head=HEAD # Mehrere Targets parallel auf affected npx nx affected -t build test lint --parallel=3 --base=main

GitHub Actions CI mit affected

# .github/workflows/ci.yml name: CI on: push: branches: [main] pull_request: branches: [main] jobs: main: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 # WICHTIG: volle Git-History für affected - uses: actions/setup-node@v4 with: node-version: 20 cache: npm - run: npm ci - uses: nrwl/nx-set-shas@v4 # Setzt NX_BASE + NX_HEAD - name: Lint affected run: npx nx affected -t lint --base=$NX_BASE --head=$NX_HEAD - name: Test affected run: npx nx affected -t test --base=$NX_BASE --head=$NX_HEAD --parallel=3 - name: Build affected run: npx nx affected -t build --base=$NX_BASE --head=$NX_HEAD --parallel=2 - name: E2E affected run: npx nx affected -t e2e --base=$NX_BASE --head=$NX_HEAD
Performance

CI-Zeitersparnis durch affected

In einem Monorepo mit 20 Projekten: Statt alle 20 Projekte zu bauen, baut affected im Schnitt nur 3-5. CI-Zeit sinkt von 45 Minuten auf 8-12 Minuten — ohne Qualitätsverlust.

nx show — Analyse-Befehle

# Alle Projekte im Workspace auflisten npx nx show projects # Projekt-Details anzeigen npx nx show project web-app # Nur affected Projekte (für Debugging) npx nx show projects --affected --base=main # Mit Type-Filter npx nx show projects --type app npx nx show projects --type lib # Mit Tag-Filter npx nx show projects --withTag "scope:web" # Output eines spezifischen Projekts npx nx show project web-app --web # Im Browser öffnen
Claude Code Tipp: "Erkläre mir, warum 'admin-app' als affected gilt, obwohl ich nur 'shared/util' geändert habe. Analysiere den Projekt-Graph." Claude verfolgt die Abhängigkeitskette und zeigt den genauen Pfad.

4. Remote Caching mit Nx Cloud

Nx Cloud erweitert das lokale Nx-Caching auf eine verteilte Cloud-Infrastruktur. Einmal gebaute Artefakte werden geteilt — zwischen lokalen Entwicklungsumgebungen, CI-Runs und verschiedenen Branches.

Nx Cloud einrichten

# Nx Cloud verbinden (interaktiv) npx nx connect # Oder direkt mit Access Token npx nx connect --accessToken=NXC_XXXXXXXXXXXXXXXX # @nrwl/nx-cloud installieren npm install @nrwl/nx-cloud # nx.json wird automatisch aktualisiert: { "nxCloudAccessToken": "NXC_XXXXXXXXXXXXXXXX" }

Cache Hit vs. Cache Miss verstehen

# Erster Build — Cache Miss (muss berechnet werden) npx nx build web-app Output: web-app:build # starting web-app:build # 23.4s ✓ web-app:build [local cache miss] # Zweiter Build — Cache Hit (instant) npx nx build web-app Output: web-app:build # starting ✓ web-app:build [remote cache hit] 0.3s # Cache-Status anzeigen npx nx build web-app --verbose # Cache für spezifisches Projekt leeren npx nx reset
Nx Cloud

Was wird gecacht?

Der Cache-Key berechnet sich aus: Quelldateien + Dependencies + Node-Version + Target-Konfiguration.

Distributed Task Execution (DTE)

DTE verteilt Nx-Tasks auf mehrere CI-Agents — Nx Cloud koordiniert die Verteilung und aggregiert die Ergebnisse:

# .github/workflows/ci-dte.yml — DTE mit 3 Agents name: CI mit DTE on: [push, pull_request] jobs: agents: name: Agent ${{ matrix.agent }} runs-on: ubuntu-latest strategy: matrix: agent: [1, 2, 3] steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: node-version: 20 cache: npm - run: npm ci - name: Start Nx Agent run: npx nx-cloud start-agent env: NX_AGENT_NAME: agent-${{ matrix.agent }} main: runs-on: ubuntu-latest needs: [agents] steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - uses: actions/setup-node@v4 with: node-version: 20 cache: npm - run: npm ci - uses: nrwl/nx-set-shas@v4 - name: Run Distributed CI run: npx nx-cloud record -- npx nx affected -t build test lint --parallel=3 env: NX_CLOUD_DISTRIBUTED_EXECUTION: true
# Cache-Statistiken aus Nx Cloud Dashboard Tasks run: 847 Remote cache hits: 631 (74.5%) Time saved: 2h 14min Avg task duration: 8.3s → 2.1s (mit Cache)

Self-hosted Nx Cache (ohne Cloud)

# nx.json — eigener Cache-Server { "tasksRunnerOptions": { "default": { "runner": "@nrwl/nx-cloud", "options": { "cacheableOperations": [ "build", "test", "lint", "e2e" ], "accessToken": "${NX_CLOUD_ACCESS_TOKEN}", "url": "https://my-nx-cache.company.com" } } } }
Claude Code Tipp: "Zeige mir alle Targets in meinem Workspace die noch kein Caching aktiviert haben und erkläre, ob sie cache-fähig sind."

5. Generators & Executors

Generators automatisieren repetitive Aufgaben wie das Erstellen neuer Bibliotheken, Komponenten oder Services. Executors definieren, wie Tasks ausgeführt werden. Beide lassen sich für den eigenen Workspace anpassen.

Eingebaute Generators nutzen

# Neue React-Bibliothek erstellen npx nx generate @nx/react:library feature-auth \ --directory=libs/feature/auth \ --tags=scope:web,type:feature \ --unitTestRunner=vitest \ --bundler=vite \ --style=css # React-Komponente in bestehender Lib npx nx generate @nx/react:component LoginForm \ --project=feature-auth \ --export=true \ --style=css # Node.js Backend-App npx nx generate @nx/node:application api \ --directory=apps/api \ --framework=express \ --unitTestRunner=jest # TypeScript-Utility-Lib npx nx generate @nx/js:library shared-utils \ --directory=libs/shared/utils \ --bundler=tsc \ --tags=scope:shared,type:util

Custom Generator erstellen

Für wiederholende Projektmuster lohnt sich ein eigener Generator:

# Generator-Scaffolding erstellen npx nx generate @nx/plugin:generator my-lib-generator \ --project=tools # Generierte Struktur: tools/generators/my-lib-generator/ ├── schema.json # Generator-Interface ├── schema.d.ts # TypeScript-Typen ├── generator.ts # Implementierung └── files/ # Template-Dateien └── src/ └── index.ts__template__
// tools/generators/my-lib-generator/schema.json { "$schema": "http://json-schema.org/schema", "cli": "nx", "id": "my-lib-generator", "title": "My Library Generator", "type": "object", "properties": { "name": { "type": "string", "description": "Library name", "$default": { "$source": "argv", "index": 0 }, "x-prompt": "What is the library name?" }, "scope": { "type": "string", "description": "Library scope", "enum": ["web", "admin", "shared"], "x-prompt": "Which scope?" }, "type": { "type": "string", "description": "Library type", "enum": ["feature", "ui", "data-access", "util"], "x-prompt": "Which type?" } }, "required": ["name", "scope", "type"] }
// tools/generators/my-lib-generator/generator.ts import { Tree, formatFiles, generateFiles, addProjectConfiguration, names, offsetFromRoot } from '@nx/devkit'; import * as path from 'path'; import { MyLibGeneratorSchema } from './schema'; export default async function (tree: Tree, options: MyLibGeneratorSchema) { const { name, scope, type } = options; const projectName = `${scope}-${name}`; const projectRoot = `libs/${scope}/${name}`; const projectNames = names(projectName); // Nx-Projektkonfiguration hinzufügen addProjectConfiguration(tree, projectName, { root: projectRoot, projectType: 'library', sourceRoot: `${projectRoot}/src`, tags: [`scope:${scope}`, `type:${type}`], targets: { build: { executor: '@nx/js:tsc', outputs: ['{options.outputPath}'], options: { outputPath: `dist/${projectRoot}`, main: `${projectRoot}/src/index.ts`, tsConfig: `${projectRoot}/tsconfig.lib.json` } }, test: { executor: '@nx/vitest:vitest' } } }); // Template-Dateien generieren generateFiles( tree, path.join(__dirname, 'files'), projectRoot, { ...projectNames, scope, type, offsetFromRoot: offsetFromRoot(projectRoot), template: '' } ); // Formatierung anwenden await formatFiles(tree); } // Generator ausführen: // npx nx generate tools:my-lib-generator --name=cart --scope=web --type=feature
Claude Code Tipp: "Erstelle mir einen Nx Generator der automatisch eine Data-Access-Lib mit Zustand-Store, API-Service und TypeScript-Interfaces generiert." Claude schreibt den kompletten Generator inklusive schema.json und Template-Dateien.

6. Module Federation

Module Federation ermöglicht es, eine Micro-Frontend-Architektur aufzubauen: Mehrere unabhängige Apps teilen sich zur Laufzeit Komponenten und Bibliotheken. Nx hat erstklassige Unterstützung für Module Federation mit React und Angular.

Host- und Remote-Apps einrichten

# Host-App (Shell) generieren npx nx generate @nx/react:host shell \ --directory=apps/shell \ --remotes=checkout,cart,account \ --bundler=webpack \ --style=css # Remote-App (Micro-Frontend) generieren npx nx generate @nx/react:remote checkout \ --directory=apps/checkout \ --host=shell \ --bundler=webpack \ --style=css # Weitere Remotes hinzufügen npx nx generate @nx/react:remote cart \ --directory=apps/cart \ --host=shell \ --bundler=webpack # Alle Apps starten (Shell + alle Remotes) npx nx serve shell --devRemotes=checkout,cart

webpack.config.js der Host-App

// apps/shell/webpack.config.js const { withModuleFederation } = require('@nx/react/module-federation'); const config = require('./module-federation.config'); module.exports = withModuleFederation(config); // apps/shell/module-federation.config.ts import { ModuleFederationConfig } from '@nx/react/module-federation'; const config: ModuleFederationConfig = { name: 'shell', remotes: [ ['checkout', 'http://localhost:4201/'], ['cart', 'http://localhost:4202/'], ['account', 'http://localhost:4203/'] ], shared: (libName, defaultConfig) => { // react und react-dom immer singleton if (libName === 'react' || libName === 'react-dom') { return { ...defaultConfig, singleton: true, eager: true }; } return defaultConfig; } }; export default config;

Remote-App Konfiguration

// apps/checkout/module-federation.config.ts import { ModuleFederationConfig } from '@nx/react/module-federation'; const config: ModuleFederationConfig = { name: 'checkout', exposes: { './Module': './src/remote-entry.ts' } }; export default config; // apps/checkout/src/remote-entry.ts export { CheckoutModule } from './app/checkout/checkout.module';

Dynamic Remote Loading

// apps/shell/src/app/app.tsx — dynamisches Laden import { lazy, Suspense } from 'react'; // Lazy load remote modules const CheckoutApp = lazy(() => import('checkout/Module')); const CartApp = lazy(() => import('cart/Module')); export function App() { return ( <Suspense fallback={<div>Loading...</div>}> <Routes> <Route path="/checkout/*" element={<CheckoutApp />} /> <Route path="/cart/*" element={<CartApp />} /> </Routes> </Suspense> ); }

Shared Libraries in Module Federation

// libs/shared/ui — wird EINMAL geladen, von allen Remotes genutzt // Nx stellt sicher dass shared libs nur einmal im Bundle sind // module-federation.config.ts (alle Apps) const config: ModuleFederationConfig = { name: 'checkout', exposes: { './Module': './src/remote-entry.ts' }, shared: (libName) => { // Shared Nx-Libs singleton markieren if (libName === '@my-workspace/shared/ui') { return { singleton: true, strictVersion: false }; } return false; // Andere Libs nicht teilen } }; # Alle Micro-Frontends starten npx nx run-many -t serve --projects=shell,checkout,cart,account --parallel # Production Build aller Remotes npx nx run-many -t build --projects=checkout,cart,account --parallel npx nx build shell # Shell zuletzt bauen
Module Federation

Wann Module Federation?

Szenario Module Federation?
Mehrere Teams, unabhängige Deploys Ja, ideal
Große App, ein Team Eher nicht — normale Libs reichen
Legacy-Migration schrittweise Ja, sehr geeignet
Unterschiedliche Framework-Versionen Ja, wenn unumgänglich
Claude Code Tipp: "Analysiere meine Module-Federation-Konfiguration und zeige mir, welche Shared Libraries potenzielle Version-Konflikte haben könnten." Claude prüft alle module-federation.config.ts Dateien und erkennt Singleton-Probleme.

Fazit: Claude Code als Nx-Experte im Team

Nx ist eines der komplexesten Werkzeuge im JavaScript-Ökosystem. Die Konfigurationsdateien sind tief verschachtelt, die Abhängigkeitsketten nicht immer intuitiv, und neue Features wie DTE oder Module Federation haben steile Lernkurven. Claude Code verändert das fundamentally:

Der größte Mehrwert: Claude kennt die gesamte Workspace-Konfiguration im Kontext und kann Fragen beantworten, die über einzelne Dateien hinausgehen. Das macht Nx — das mächtigste TypeScript-Monorepo-Tool — auch für kleinere Teams beherrschbar.

Monorepo-Modul im Kurs

Im Claude Code Mastery Kurs: vollständiges Nx-Modul mit Affected Commands, Remote Caching, Custom Generators und Module Federation für TypeScript-Monorepos.

14 Tage kostenlos testen →