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?
- Build-Artefakte (dist/)
- Test-Ergebnisse
- Lint-Outputs
- Alle Targets mit
"cache": true in targetDefaults
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:
- Projekt-Graph-Analyse: Claude versteht die Abhängigkeitsketten und beantwortet "Warum ist Projekt X affected?" direkt aus dem Kontext.
- Konfigurationsoptimierung: nx.json, targetDefaults, Cache-Konfiguration — Claude erkennt Inkonsistenzen und schlägt Verbesserungen vor.
- Generator-Entwicklung: Custom Generators mit schema.json, Tree-API und formatFiles schreibt Claude in Minuten statt Stunden.
- Module Federation Debugging: Singleton-Konflikte, Shared-Library-Probleme, Dynamic-Loading-Fehler — Claude analysiert webpack.config.js und findet Root Causes.
- CI-Optimierung: GitHub Actions Workflows mit nrwl/nx-set-shas und DTE-Konfiguration generiert Claude anhand des aktuellen Workspace-Aufbaus.
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 →