Nx vs. Turborepo für TypeScript-Monorepos: Build-Caching im Vergleich
AI generated
<T>
type
TypeScript · Monorepo · Build-Tooling
Nx vs. Turborepo für TypeScript-Monorepos
Build-Caching und Task-Pipelines im Vergleich

Ein TypeScript-Monorepo ohne Build-Orchestrierung baut bei jedem Commit alle Pakete neu, auch wenn sich nur eine Zeile in einem einzigen Paket geändert hat. Nx und Turborepo lösen dieses Problem mit Task-Graphen, Caching und Affected-Detection, aber mit unterschiedlicher Philosophie und unterschiedlichem Funktionsumfang.

18 Min. Lesezeit Nx · Turborepo · Task-Pipelines · Caching TypeScript 5.x · Node.js 20+

1. Warum Build-Orchestrierung im TypeScript-Monorepo zum Problem wird

Sobald ein TypeScript-Monorepo über eine Handvoll Pakete hinauswächst, reicht ein einfacher npm run build pro Paket nicht mehr aus. Ohne Orchestrierung baut jedes CI-Skript sämtliche Pakete in der falschen Reihenfolge, ignoriert Abhängigkeiten zwischen ihnen oder wiederholt Arbeit, die sich seit dem letzten Lauf gar nicht verändert hat. Bei zehn Paketen mag das noch tolerierbar sein, bei fünfzig Paketen wird jede Pipeline zur Geduldsprobe und jeder Pull Request wartet minutenlang auf einen Build, der zu neunzig Prozent redundant ist.

Genau hier setzen Nx und Turborepo an. Beide analysieren die Abhängigkeitsstruktur eines TypeScript-Monorepos als gerichteten Graphen, erkennen, welche Pakete von einer Änderung betroffen sind, und cachen das Ergebnis abgeschlossener Tasks. Der Unterschied liegt nicht in der Grundidee, sondern im Funktionsumfang: Nx bringt ein komplettes Ökosystem aus Generatoren, Plugins und einer eigenen Editor-Erweiterung mit, während Turborepo bewusst schlank bleibt und sich auf Task-Ausführung und Caching konzentriert. Welches Werkzeug besser passt, hängt stark von der Größe des Teams und der gewünschten Kontrolle über den Build-Prozess ab.

2. Nx im Überblick: Projekt-Graph und Generatoren

Nx wurde ursprünglich von einem ehemaligen Angular-Team entwickelt und versteht sich als vollwertige Entwicklungsplattform für ein TypeScript-Monorepo, nicht nur als Build-Runner. Der Kern ist der Projekt-Graph: Nx scannt jedes package.json und jede project.json, liest Importe zwischen Paketen aus und baut daraus eine vollständige Abhängigkeitskarte. Der Befehl nx graph visualisiert dieses Netz interaktiv im Browser, was besonders bei der Einarbeitung neuer Teammitglieder in ein gewachsenes Monorepo hilft.

Zusätzlich bringt Nx Generatoren mit, die neue Pakete, Bibliotheken oder Komponenten nach vordefinierten Schablonen erzeugen. Ein nx generate @nx/js:library shared-utils legt in Sekunden ein neues Paket mit korrekt konfiguriertem tsconfig.json, Testsetup und Lint-Regeln an, konsistent zu allen anderen Paketen im TypeScript-Monorepo. Das reduziert Copy-Paste-Fehler beim Anlegen neuer Pakete erheblich, bringt aber auch eine gewisse Lernkurve mit, weil Nx eigene Konventionen für Projektstruktur und Konfiguration durchsetzt.


{
  "targetDefaults": {
    "build": {
      "dependsOn": ["^build"],
      "inputs": ["production", "^production"],
      "outputs": ["{projectRoot}/dist"],
      "cache": true
    },
    "test": {
      "inputs": ["default", "^production"],
      "cache": true
    },
    "lint": {
      "inputs": ["default", "{workspaceRoot}/.eslintrc.json"],
      "cache": true
    }
  },
  "namedInputs": {
    "default": ["{projectRoot}/**/*", "sharedGlobals"],
    "production": ["default", "!{projectRoot}/**/*.spec.ts"]
  }
}

3. Turborepo im Überblick: Pipelines und Minimalismus

Turborepo verfolgt einen bewusst schmaleren Ansatz für ein TypeScript-Monorepo. Statt eigene Generatoren, Plugins oder eine Editor-Integration mitzubringen, konzentriert sich das Tool auf genau eine Aufgabe: Tasks aus den package.json-Skripten der einzelnen Workspaces in der richtigen Reihenfolge ausführen und dabei so viel wie möglich cachen. Die Konfiguration lebt in einer einzigen Datei, turbo.json, die pro Task definiert, wovon er abhängt und welche Outputs gecacht werden sollen.

Dieser Minimalismus ist gleichzeitig die größte Stärke und die größte Grenze von Turborepo. Teams, die bereits mit npm-, pnpm- oder Yarn-Workspaces arbeiten und lediglich schnellere, gecachte Builds wollen, sind mit Turborepo oft in unter einer Stunde produktiv, weil keine bestehende Projektstruktur umgebaut werden muss. Wer hingegen ein komplettes Tooling-Ökosystem mit Codegenerierung und eingebauten Migrations-Skripten für ein wachsendes TypeScript-Monorepo sucht, wird bei Turborepo allein nicht fündig und muss zusätzliche Werkzeuge kombinieren.


{
  "$schema": "https://turbo.build/schema.json",
  "pipeline": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**"]
    },
    "test": {
      "dependsOn": ["build"],
      "outputs": [],
      "inputs": ["src/**/*.ts", "test/**/*.ts"]
    },
    "lint": {
      "outputs": []
    },
    "dev": {
      "cache": false,
      "persistent": true
    }
  }
}

4. Task-Pipelines konfigurieren: dependsOn und Outputs

Der zentrale Mechanismus in beiden Werkzeugen ist die deklarative Beschreibung von Task-Abhängigkeiten. Der Schlüssel dependsOn mit dem Präfix ^ bedeutet in beiden Systemen dasselbe: Führe diesen Task zuerst in allen Paketen aus, von denen das aktuelle Paket abhängt. Ein build-Task mit dependsOn: ["^build"] stellt sicher, dass eine gemeinsam genutzte Bibliothek immer vor dem Paket gebaut wird, das sie importiert, ohne dass ein Entwickler die Reihenfolge manuell in einem Shell-Skript pflegen muss.

Das Feld outputs ist ebenso entscheidend, weil es dem Cache mitteilt, welche Dateien nach einem erfolgreichen Task gespeichert werden müssen. Fehlt ein Verzeichnis wie dist/** in der Konfiguration, funktioniert das Caching zwar formal, liefert aber beim Cache-Hit keine tatsächlichen Build-Artefakte zurück. Ein häufiger Anfängerfehler in einem TypeScript-Monorepo ist, den outputs-Pfad falsch zu setzen, etwa weil der tsconfig.json-Wert für outDir nicht mit dem in der Pipeline hinterlegten Pfad übereinstimmt.

5. Caching im Detail: lokal, remote und Invalidierung

Lokales Caching funktioniert bei beiden Werkzeugen ähnlich: Aus den Eingabedateien eines Tasks, den relevanten Umgebungsvariablen und der Task-Definition selbst wird ein Hash berechnet. Ändert sich keine dieser Eingaben, liefert Nx oder Turborepo das zwischengespeicherte Ergebnis direkt aus dem lokalen Dateisystem zurück, ohne den Task erneut auszuführen. Das beschleunigt wiederholte lokale Builds in einem TypeScript-Monorepo erheblich, besonders beim Wechsel zwischen Branches, wenn viele Pakete unverändert geblieben sind.

Interessanter wird es beim Remote Caching, denn hier trennen sich die Wege der beiden Tools finanziell und technisch. Nx Cloud bietet einen gehosteten Remote Cache mit Distributed Task Execution über mehrere CI-Maschinen hinweg. Turborepo unterstützt Remote Caching über Vercels Infrastruktur oder selbst gehostete Alternativen wie eine eigene S3-kompatible Implementierung. In beiden Fällen gilt: Ein Cache-Hit in der CI-Pipeline eines TypeScript-Monorepos spart nicht nur Zeit, sondern auch tatsächliche Rechenkosten, wenn Build-Minuten nach Verbrauch abgerechnet werden.


# Nx: lokalen und Remote-Cache-Status prüfen
npx nx build shared-utils --skip-nx-cache=false

# Nx Cloud verbinden (Remote Cache aktivieren)
npx nx connect-to-nx-cloud

# Turborepo: Remote Cache mit Vercel verbinden
npx turbo login
npx turbo link

# Cache absichtlich umgehen, z. B. nach Dependency-Update
npx turbo run build --force

6. Affected-Commands: nur betroffene Pakete bauen

Der größte praktische Gewinn für ein wachsendes TypeScript-Monorepo liegt in den Affected-Commands. Statt bei jedem Pull Request alle Pakete zu bauen und zu testen, berechnen beide Werkzeuge anhand des Git-Diffs, welche Pakete sich direkt oder transitiv geändert haben. Nx nutzt dafür nx affected --target=test und vergleicht standardmäßig gegen den main-Branch. Turborepo erreicht dasselbe über turbo run test --filter=...[main], eine Filtersyntax, die auf den ersten Blick weniger intuitiv wirkt, aber dieselbe Git-basierte Berechnung durchführt.

Bei einem TypeScript-Monorepo mit fünfzig Paketen, von denen ein Pull Request typischerweise nur zwei bis drei berührt, reduziert sich die CI-Laufzeit dadurch oft auf ein Zehntel der Zeit, die ein vollständiger Build benötigen würde. Wichtig ist dabei, den Vergleichs-Branch korrekt zu konfigurieren, denn ein falsch gesetzter Base-Commit führt entweder dazu, dass zu viele Pakete als betroffen markiert werden, oder schlimmer, dass tatsächlich betroffene Pakete übersehen werden und ein Fehler unbemerkt in die Produktion gelangt.

7. Migration von reinen Workspaces zu Nx oder Turborepo

Ein bestehendes TypeScript-Monorepo, das bislang nur auf npm- oder pnpm-Workspaces basiert, lässt sich mit beiden Werkzeugen inkrementell migrieren. Turborepo benötigt lediglich eine turbo.json im Root und funktioniert danach sofort über die bestehenden package.json-Skripte, ohne dass Paketstrukturen verändert werden müssen. Der Migrationsaufwand liegt oft bei wenigen Stunden, weil Turborepo die vorhandene Workspace-Konfiguration respektiert, statt sie zu ersetzen.

Nx bietet mit npx nx init einen ähnlichen sanften Einstieg, der bestehende Skripte automatisch erkennt und in Nx-Targets überführt, ohne sofort die vollständige Nx-Projektstruktur mit project.json-Dateien zu erzwingen. Für Teams, die später auch Generatoren und den Projekt-Graphen nutzen wollen, lohnt sich der schrittweise Umstieg auf die vollständige Nx-Konfiguration, weil dann zusätzliche Analysefunktionen wie das automatische Erkennen zirkulärer Abhängigkeiten im TypeScript-Monorepo verfügbar werden.

8. Integration in CI/CD-Pipelines

In der CI-Pipeline zahlt sich die Investition in Nx oder Turborepo am deutlichsten aus. Der übliche Aufbau besteht aus einem einzigen Checkout, gefolgt von einem Affected-Command für Lint, Test und Build, statt drei separaten Jobs, die jedes Paket einzeln durchlaufen. Damit der Cache zwischen CI-Läufen wiederverwendet werden kann, muss entweder ein Remote Cache konfiguriert sein oder der lokale Cache-Ordner explizit zwischen Pipeline-Stages persistiert werden, etwa über GitLab-CI-Caches oder GitHub-Actions-Artefakte.

Ein Detail, das in der Praxis häufig übersehen wird: Damit Affected-Commands in der CI korrekt funktionieren, braucht die Pipeline Zugriff auf die vollständige Git-Historie, nicht nur den letzten Commit. Ein flacher Checkout mit --depth=1 verhindert, dass Nx oder Turborepo den Vergleichs-Commit findet, und führt dazu, dass fälschlicherweise das gesamte TypeScript-Monorepo als betroffen markiert wird. Der Fix ist meist simpel: fetch-depth: 0 in GitHub Actions oder GIT_DEPTH: 0 in GitLab-CI setzen.

9. Nx vs. Turborepo im direkten Vergleich

Beide Werkzeuge lösen das Kernproblem eines TypeScript-Monorepos, unterscheiden sich aber deutlich in Umfang, Lernkurve und Betriebsmodell. Die folgende Übersicht fasst die wichtigsten Entscheidungskriterien zusammen.

Kriterium Nx Turborepo Praxis-Hinweis
Setup-Aufwand Mittel bis hoch, eigenes Ökosystem Niedrig, eine Konfigdatei Turborepo bei kleinen Teams schneller startklar
Codegenerierung Generatoren für Pakete, Libs, Komponenten Nicht eingebaut Relevant bei häufigem Anlegen neuer Pakete
Projekt-Graph-Visualisierung nx graph im Browser Nur textuelle Ausgabe Hilfreich bei großen, unübersichtlichen Repos
Remote Caching Nx Cloud, kostenpflichtig ab bestimmtem Volumen Vercel oder selbst gehostet Kostenmodell vor Skalierung genau prüfen
Distributed Task Execution Ja, über Nx Cloud Agents Nein, nur lokales Caching plus Remote Cache Wichtig ab sehr großen Testsuiten

Für kleine bis mittlere Teams, die vor allem schnellere, gecachte Builds ohne große Umstellung wollen, ist Turborepo meist der pragmatischere Einstieg in ein TypeScript-Monorepo. Größere Organisationen mit vielen Teams, häufig neu entstehenden Paketen und dem Bedarf nach verteilter Testausführung profitieren stärker vom vollständigen Nx-Ökosystem, akzeptieren dafür aber eine steilere Lernkurve und mehr Konfigurationsaufwand zu Beginn.

Mironsoft

TypeScript-Architektur, Monorepo-Tooling und CI/CD-Optimierung

TypeScript-Monorepo mit langsamen CI-Läufen?

Wir analysieren bestehende Workspace-Strukturen, führen Nx oder Turborepo passend zur Teamgröße ein und richten Affected-Builds sowie Remote Caching für spürbar kürzere Pipeline-Laufzeiten ein.

Tooling-Auswahl

Nx oder Turborepo passend zu Teamgröße und Wachstumsplan bewerten

Migration

Bestehende Workspaces schrittweise auf Task-Pipelines umstellen

CI-Optimierung

Affected-Builds und Remote Caching in GitLab-CI oder GitHub Actions

10. Zusammenfassung

Nx und Turborepo lösen dasselbe Grundproblem eines wachsenden TypeScript-Monorepos: unnötige, redundante Builds über einen Task-Graphen, Caching und Affected-Detection zu vermeiden. Turborepo überzeugt durch Minimalismus und schnellen Einstieg über eine einzige turbo.json, während Nx ein vollständiges Ökosystem mit Generatoren, Projekt-Graph-Visualisierung und Distributed Task Execution bietet. Die Wahl zwischen beiden ist selten falsch, solange sie zur tatsächlichen Teamgröße und Wachstumsgeschwindigkeit passt.

Unabhängig vom gewählten Werkzeug gilt: Affected-Commands und korrekt konfigurierte Outputs sind die zwei Hebel mit dem größten Effekt auf die CI-Laufzeit eines TypeScript-Monorepos. Wer diese beiden Punkte sauber einrichtet, sieht meist innerhalb der ersten Woche eine spürbare Reduktion der Pipeline-Dauer, lange bevor komplexere Themen wie Remote Caching oder verteilte Testausführung überhaupt relevant werden.

Nx vs. Turborepo — Das Wichtigste auf einen Blick

Task-Graph

Beide Tools bauen einen Abhängigkeitsgraphen aus Paket-Importen und führen Tasks in korrekter Reihenfolge über dependsOn aus.

Caching

Hash aus Inputs, Umgebung und Task-Definition entscheidet über Cache-Hit. outputs muss korrekt gesetzt sein.

Affected-Commands

Git-Diff-basierte Erkennung betroffener Pakete reduziert CI-Zeit oft auf einen Bruchteil eines vollständigen Builds.

Entscheidungshilfe

Turborepo für schnellen, schlanken Einstieg. Nx für Generatoren, Projekt-Graph und verteilte Testausführung.

11. FAQ: Nx vs. Turborepo für TypeScript-Monorepos

1Was ist der grundlegende Unterschied?
Nx ist eine vollständige Plattform mit Generatoren und Projekt-Graph. Turborepo konzentriert sich auf Task-Ausführung und Caching über eine Konfigdatei.
2Beide Tools kombinieren?
Technisch möglich, aber nicht empfohlen wegen doppelter Caches und Konfiguration. Auf ein Tool festlegen.
3outputs fehlt in der Pipeline?
Task wird gecacht, aber Artefakte werden beim Cache-Hit nicht wiederhergestellt. Nachfolgende Tasks finden leere Verzeichnisse vor.
4Ganzes Monorepo als betroffen markiert?
Meist ein flacher Git-Checkout mit --depth=1. fetch-depth: 0 oder GIT_DEPTH: 0 in der CI setzen.
5Remote Caching für kleine Teams?
Amortisiert sich oft erst später bei wenigen CI-Läufen. Lokales Caching bringt bereits spürbare Verbesserungen.
6Migration von npm-Workspaces?
turbo.json im Root anlegen, Pipelines für bestehende Skripte ergänzen, danach turbo run build aufrufen. Struktur bleibt unverändert.
7Verteilte Testausführung mit Turborepo?
Nein, nur lokales und Remote Caching. Für Distributed Task Execution ist Nx mit Nx Cloud Agents passender.
8Bestimmte Projektstruktur für Generatoren?
Am zuverlässigsten mit project.json pro Paket. Ohne diese Dateien sind Generatoren eingeschränkt nutzbar.
9Wie wird der Cache-Hash berechnet?
Aus Inputs, Umgebungsvariablen und Task-Definition. Ändert sich nichts davon, wird das gecachte Ergebnis geliefert.
10Vorteil des Nx-Projekt-Graphen?
Interaktive, visuelle Darstellung im Browser erleichtert die Einarbeitung in große TypeScript-Monorepos.