Monorepo Vue-Projekte mit Turborepo: Caching, Pipelines
AI generated
<v/>
{ }
Vue.js · Turborepo · Monorepo · CI/CD
Monorepo Vue-Projekte mit Turborepo
Caching und Build-Pipelines für mehrere Apps in einem Repository

Mehrere Vue-Anwendungen und geteilte Pakete in einem Repository zu verwalten, wird ohne Task-Orchestrierung schnell langsam und unübersichtlich. Turborepo bringt inkrementelles Caching, parallele Task-Ausführung und eine deklarative Pipeline-Definition, die Build-Zeiten in Vue-Monorepos von Minuten auf Sekunden reduziert.

19 Min. Lesezeit Workspaces · Caching · Pipelines · CI Turborepo · pnpm · Vue 3 · Vite

1. Warum Vue-Projekte überhaupt ins Monorepo wandern

Ein Monorepo für Vue-Projekte bündelt mehrere Anwendungen und geteilte Pakete, etwa ein Design System oder gemeinsame TypeScript-Typen, in einem einzigen Repository, statt sie über mehrere separate Git-Repositories zu verteilen. Der unmittelbare Vorteil: Eine Änderung an einer geteilten Vue-Komponente wird in derselben Pull Request sichtbar, die auch die konsumierenden Anwendungen anpasst, statt über mehrere Repositories mit separaten Versionsbumps synchronisiert werden zu müssen.

Ohne geeignetes Tooling wird ein Vue-Monorepo jedoch schnell zum Performance-Problem: Ein einfacher npm run build im Root-Verzeichnis baut standardmäßig alle Pakete neu, selbst wenn nur eine einzige Datei in einer einzigen Vue-Anwendung geändert wurde. Bei zehn oder mehr Paketen summiert sich das zu Build-Zeiten, die jeden Entwicklungsvorteil des Monorepos wieder zunichtemachen. Genau hier setzt Turborepo an: Es orchestriert Tasks über Paketgrenzen hinweg und baut nur das, was sich tatsächlich geändert hat.

Der Umstieg auf ein Monorepo mit Turborepo lohnt sich für Vue-Teams besonders dann, wenn mehrere Anwendungen dieselben internen Pakete nutzen, etwa ein Shop-Frontend und ein Admin-Tool, die beide auf demselben Design-System und denselben API-Client-Typen aufbauen. Für ein einzelnes Vue-Projekt ohne geteilte Pakete lohnt sich der zusätzliche Tooling-Aufwand dagegen selten.

2. Workspace-Struktur mit pnpm und Turborepo

Die gängige Struktur für ein Vue-Monorepo mit Turborepo trennt Anwendungen und geteilte Pakete in zwei Top-Level-Ordner, üblicherweise apps/ und packages/. Jede Vue-Anwendung in apps/ hat ihr eigenes package.json mit eigenen Abhängigkeiten, während wiederverwendbare Bausteine wie das Design System, ein API-Client oder geteilte ESLint-Konfiguration in packages/ liegen und von den Anwendungen als interne Workspace-Abhängigkeiten referenziert werden.

pnpm ist der verbreitetste Paketmanager für Turborepo-Monorepos, weil sein Workspace-Protokoll (workspace:*) interne Pakete per Symlink verknüpft, statt sie über die Registry aufzulösen. Das bedeutet: Eine Änderung an einer geteilten Vue-Komponente ist sofort in der konsumierenden Anwendung sichtbar, ohne einen Publish-Schritt zwischen den Paketen.


// pnpm-workspace.yaml — Defines which folders are part of the monorepo
// packages:
//   - "apps/*"
//   - "packages/*"

// apps/shop/package.json — Consuming an internal package via workspace protocol
{
  "name": "@mironsoft/shop",
  "dependencies": {
    "@mironsoft/design-system": "workspace:*",
    "@mironsoft/api-client": "workspace:*",
    "vue": "^3.4.0"
  }
}

// packages/design-system/package.json — Internal package, never published externally
{
  "name": "@mironsoft/design-system",
  "main": "./dist/index.js",
  "peerDependencies": { "vue": "^3.4.0" }
}

3. Task-Pipelines deklarativ definieren

Das Herzstück von Turborepo ist die Datei turbo.json, in der definiert wird, wie einzelne Tasks, etwa build, test oder lint, voneinander abhängen. Die Syntax "dependsOn": ["^build"] bedeutet, dass der build-Task eines Pakets erst startet, nachdem der build-Task aller Pakete abgeschlossen ist, von denen es abhängt. Für ein Vue-Monorepo bedeutet das konkret: Die Shop-Anwendung wartet automatisch darauf, dass das Design-System-Paket fertig gebaut ist, bevor ihr eigener Build startet.

Turborepo leitet diesen Abhängigkeitsgraphen automatisch aus den package.json-Dependencies ab, ohne dass er manuell gepflegt werden muss. Das ist ein erheblicher Vorteil gegenüber selbst gebauten Skript-Lösungen, bei denen die Build-Reihenfolge häufig hart kodiert und bei jedem neuen Paket manuell nachgepflegt werden muss.


// turbo.json — Declarative pipeline definition for a Vue monorepo
{
  "$schema": "https://turbo.build/schema.json",
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": ["dist/**", ".output/**"]
    },
    "test": {
      "dependsOn": ["^build"],
      "outputs": ["coverage/**"]
    },
    "lint": {
      "dependsOn": []
    },
    "dev": {
      "cache": false,
      "persistent": true
    }
  }
}

4. Inkrementelles Caching verstehen und nutzen

Der eigentliche Performance-Gewinn von Turborepo in Vue-Monorepos entsteht durch inkrementelles Caching. Turborepo berechnet einen Hash aus allen Eingaben eines Tasks, dazu zählen der Quellcode des Pakets, seine Abhängigkeiten und die relevanten Umgebungsvariablen. Ändert sich keine dieser Eingaben zwischen zwei Läufen, liest Turborepo das Ergebnis, etwa den dist-Ordner einer Vue-Anwendung, direkt aus dem Cache, statt den Build erneut auszuführen.

Für Vue-Projekte ist besonders wichtig, im outputs-Feld jeder Task-Definition exakt anzugeben, welche Verzeichnisse als Build-Ergebnis gelten, üblicherweise dist/** bei Vite-Builds. Fehlt diese Angabe, cached Turborepo zwar die Ausführung, kann das Ergebnis aber nicht wiederherstellen, was bei einem Cache-Hit zu einem fehlenden dist-Ordner führt. Ein zweiter wichtiger Punkt: Umgebungsvariablen, die das Build-Ergebnis beeinflussen, etwa die API-Basis-URL, müssen explizit in der env-Konfiguration gelistet werden, sonst invalidiert eine geänderte Variable den Cache nicht und ein veralteter Build wird ausgeliefert.

5. Remote-Caching für das ganze Team

Lokales Caching beschleunigt nur wiederholte Builds auf derselben Maschine. Remote-Caching in Turborepo teilt die Cache-Ergebnisse über Vercel Remote Cache oder einen selbst gehosteten Cache-Server mit dem gesamten Team und der CI-Pipeline. Baut ein Entwickler morgens eine Vue-Anwendung, die ein Kollege am Vortag bereits unverändert gebaut hat, lädt Turborepo das Ergebnis aus dem Remote-Cache, statt lokal neu zu bauen, selbst wenn der lokale Cache leer ist.

Der größte Hebel entsteht in der CI-Pipeline: Ein CI-Runner startet typischerweise mit leerem lokalen Cache, kann aber über Remote-Caching auf Build-Ergebnisse zugreifen, die bereits von einem vorherigen CI-Lauf oder einem lokalen Entwickler-Build erzeugt wurden. Für ein Vue-Monorepo mit mehreren Anwendungen kann das die CI-Laufzeit von zehn Minuten auf unter eine Minute reduzieren, wenn nur ein kleiner Teil der Pakete tatsächlich geändert wurde.


#!/usr/bin/env bash
# .github/workflows/ci.yml (excerpt) — Enabling Turborepo remote caching in CI
set -euo pipefail

export TURBO_TOKEN="${TURBO_TOKEN}"
export TURBO_TEAM="${TURBO_TEAM}"

# Turborepo automatically checks the remote cache before running any task
pnpm turbo run build test lint --cache-dir=.turbo

# Only re-runs tasks whose input hash changed since the last cached execution
echo "[OK] CI run complete — cache hits skip redundant Vue app rebuilds"

6. Geteilte Vue-Komponenten und Konfiguration

Ein häufiges Muster in Vue-Monorepos mit Turborepo ist ein eigenes packages/ui-Paket für geteilte Vue-Komponenten, ein packages/config-Paket für geteilte ESLint- und TypeScript-Konfiguration, und ein packages/tsconfig-Paket, das als Basis-Konfiguration von allen Anwendungen erweitert wird. Diese Struktur verhindert, dass jede Vue-Anwendung ihre eigene, leicht abweichende Lint- und TypeScript-Konfiguration pflegt.

Wichtig ist, dass geteilte Pakete selbst auch am Turborepo-Caching teilnehmen. Ein Änderung an der Basis-TypeScript-Konfiguration invalidiert automatisch den Cache aller Anwendungen, die davon abhängen, weil Turborepo den Abhängigkeitsgraphen korrekt aus den Workspace-Referenzen ableitet. Ohne diese automatische Invalidierung würde eine Konfigurationsänderung sonst unbemerkt einen veralteten, gecachten Build ausliefern.


// packages/tsconfig/vue-app.json — Base config extended by every Vue app
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "ESNext",
    "moduleResolution": "Bundler",
    "strict": true,
    "jsx": "preserve",
    "types": ["vite/client"]
  }
}

// apps/shop/tsconfig.json — Each app extends the shared base, never duplicates it
{
  "extends": "@mironsoft/tsconfig/vue-app.json",
  "compilerOptions": { "outDir": "dist" },
  "include": ["src"]
}

7. CI-Pipelines mit betroffenen Paketen (Affected)

Neben dem Task-Level-Caching bietet Turborepo mit --filter die Möglichkeit, CI-Läufe auf tatsächlich betroffene Pakete zu beschränken. Ein git diff gegen den letzten deployten Commit zeigt, welche Dateien sich geändert haben, und turbo run build --filter=...[HEAD^1] baut nur die Vue-Anwendungen und Pakete, die von diesen Änderungen tatsächlich betroffen sind, plus alle davon abhängigen Pakete.

Für ein Vue-Monorepo mit fünf Anwendungen bedeutet das: Eine Pull Request, die nur den Checkout betrifft, löst keinen Build der anderen vier Anwendungen aus, selbst ohne Caching. Kombiniert mit Remote-Caching für die tatsächlich betroffenen Pakete entsteht eine CI-Pipeline, deren Laufzeit proportional zum Umfang der Änderung ist, nicht zur Gesamtgröße des Monorepos.

8. Typische Fallstricke bei Vue-Monorepos

Der häufigste Fehler in Vue-Monorepos mit Turborepo ist ein unvollständiges outputs-Feld, das dazu führt, dass Turborepo einen Cache-Hit meldet, aber die tatsächlichen Build-Artefakte fehlen. Ein zweiter verbreiteter Fehler ist, umgebungsabhängige Werte wie API-Endpunkte nicht in der env-Konfiguration zu deklarieren, wodurch ein Staging-Build fälschlich als identisch zu einem Produktions-Build erkannt und aus dem Cache bedient wird.

Ein dritter, subtilerer Fallstrick betrifft zirkuläre Abhängigkeiten zwischen internen Vue-Paketen: Wenn das Design-System-Paket versehentlich von der Shop-Anwendung importiert, während die Shop-Anwendung gleichzeitig vom Design-System abhängt, kann Turborepo keinen gültigen Abhängigkeitsgraphen mehr berechnen. Ein Linting-Tool wie dependency-cruiser, das im CI-Prozess zirkuläre Abhängigkeiten erkennt, verhindert dieses Problem, bevor es zu kryptischen Turborepo-Fehlermeldungen führt.


#!/usr/bin/env bash
# ci/check-circular-deps.sh — Catch circular package dependencies before Turborepo does
set -euo pipefail

npx depcruise packages apps \
  --config .dependency-cruiser.js \
  --output-type err-html \
  --output-to dep-report.html

# dependency-cruiser exits non-zero when a circular dependency rule is violated
echo "[OK] No circular dependencies found between Vue monorepo packages"

9. Turborepo im Werkzeugvergleich

Für Monorepo-Tooling in Vue-Projekten stehen neben Turborepo weitere etablierte Werkzeuge zur Verfügung, die unterschiedliche Kompromisse zwischen Konfigurationsaufwand, Funktionsumfang und Ökosystem-Reife eingehen.

Werkzeug Konfigurationsaufwand Remote-Caching Besonderheit
Turborepo Niedrig Eingebaut Einfache turbo.json, schneller Einstieg
Nx Hoch Eingebaut (Nx Cloud) Umfangreichste Features, Code-Generatoren
pnpm Workspaces (allein) Minimal Keins Nur Dependency-Verknüpfung, kein Task-Caching
Lerna (klassisch) Mittel Über Nx-Integration Historisch verbreitet, heute meist mit Nx kombiniert

Für die meisten Vue-Teams, die frisch in ein Monorepo einsteigen, ist Turborepo wegen des geringen Konfigurationsaufwands und der schnellen Einrichtungszeit die pragmatischste Wahl. Nx lohnt sich, sobald zusätzlich Code-Generatoren, Dependency-Graph-Visualisierung oder tiefere IDE-Integration benötigt werden, bringt aber eine steilere Lernkurve mit sich.

Mironsoft

Monorepo-Architektur, Turborepo und schnelle Vue-Build-Pipelines

CI-Laufzeiten für euer Vue-Monorepo drastisch senken?

Wir migrieren eure Vue-Projekte in eine Turborepo-Struktur mit korrekten Task-Pipelines, Remote-Caching und affected-basierten CI-Läufen, die nur bauen, was sich tatsächlich geändert hat.

Workspace-Setup

pnpm-Workspaces mit sauberer apps/packages-Struktur aufbauen

Caching-Strategie

Turbo.json-Pipelines und Remote-Cache für Team und CI konfigurieren

CI-Optimierung

Affected-basierte Pipelines, die nur betroffene Vue-Apps bauen

10. Zusammenfassung

Monorepo Vue-Projekte mit Turborepo lösen das Skalierungsproblem, das entsteht, sobald mehrere Vue-Anwendungen und geteilte Pakete in einem Repository verwaltet werden. Eine klare apps/- und packages/-Struktur mit pnpm-Workspaces bildet das Fundament, während turbo.json deklarativ definiert, wie Tasks voneinander abhängen. Inkrementelles Caching mit korrekt konfiguriertem outputs-Feld reduziert Build-Zeiten drastisch, sobald sich Eingaben eines Pakets nicht geändert haben.

Remote-Caching teilt diese Zeitersparnis über das gesamte Team und die CI-Pipeline, während affected-basierte Filter zusätzlich sicherstellen, dass nur tatsächlich betroffene Vue-Anwendungen überhaupt gebaut werden. Wer die typischen Fallstricke bei outputs-Konfiguration, Umgebungsvariablen und zirkulären Paketabhängigkeiten vermeidet, bekommt mit Turborepo eine der pragmatischsten Lösungen für schnelle, wartbare Vue-Monorepos.

Monorepo Vue-Projekte mit Turborepo — Das Wichtigste auf einen Blick

Workspace-Struktur

apps/ für Anwendungen, packages/ für geteilte Pakete, verknüpft über pnpm-Workspaces.

Task-Pipelines

turbo.json mit dependsOn definiert Build-Reihenfolge automatisch aus dem Abhängigkeitsgraphen.

Caching

Korrektes outputs-Feld und env-Liste verhindern veraltete, fälschlich gecachte Builds.

CI-Optimierung

Remote-Caching plus --filter für affected Pakete reduzieren CI-Laufzeiten drastisch.

11. FAQ: Monorepo Vue-Projekte mit Turborepo

1Wann lohnt sich ein Vue-Monorepo?
Sobald mehrere Anwendungen dieselben internen Pakete nutzen, etwa ein Design System.
2Warum pnpm für Turborepo?
Symlink-Verknüpfung über workspace:* macht Änderungen sofort ohne Publish-Schritt sichtbar.
3Wie leitet Turborepo den Graphen ab?
Automatisch aus package.json-Dependencies kombiniert mit dependsOn in turbo.json.
4Warum genaues outputs-Feld wichtig?
Ohne es kann das Ergebnis bei einem Cache-Hit nicht wiederhergestellt werden, dist fehlt dann.
5Was bringt Remote-Caching?
Teilt Build-Ergebnisse über Team und CI, auch bei leerem lokalen Cache.
6CI-Laufzeiten weiter reduzieren?
Über --filter mit git diff, sodass nur betroffene Anwendungen gebaut werden.
7Undeklarierte Umgebungsvariablen?
Invalidieren den Cache nicht, ein veralteter Build wird fälschlich ausgeliefert.
8Zirkuläre Abhängigkeiten verhindern?
Linting-Tool wie dependency-cruiser im CI-Prozess einsetzen.
9Turborepo oder Nx?
Turborepo für schnellen Einstieg, Nx für Code-Generatoren und tiefere IDE-Integration.
10Nehmen geteilte Komponenten am Caching teil?
Ja, Änderungen invalidieren automatisch den Cache aller abhängigen Anwendungen.