5 Apps in einem Repository - Das Turborepo-Monorepo eines Solo-Entwicklers
Wie man mehrere Apps und Websites in einem einzigen Repository mit Turborepo verwaltet. Gemeinsame Konfiguration teilen, mit dem Cache Builds sparen und pro App deployen, aus der Praxis erklärt.
Das Wichtigste
Wer allein mehrere Apps baut, dessen Konfiguration zerstreut sich. Mit einem Turborepo-Monorepo teilt man eslint und tsconfig über packages, der turbo-Cache baut unveränderte Apps nicht neu, und mit --filter deployt man pro App.
Auf dieser Seite
Wer allein Apps baut, sammelt Repositories wie Staub. Eine App, eine Website, noch eine App... und irgendwann kopiert man die eslint-Konfiguration zum fünften Mal. Ich habe das alles in ein einziges Monorepo zusammengeführt.
Das echte Problem ist die verstreute Konfiguration
Bei vielen Repositories schmerzt nicht der Code am meisten, sondern die Konfiguration.
- eslint, prettier und tsconfig in jedes Repository kopieren
- Eine Regel ändern heißt: alle Repositories der Reihe nach anfassen
- Gemeinsame UI und Utilities sind zu klein, um sie auf npm zu veröffentlichen
Ein Monorepo dreht das um: Alles wird an einer Stelle geteilt.
Die Grundstruktur
apps/
├── web/ # 랜딩
├── blog/ # 이 블로그
├── wave/ # 오늘의 파도
└── ...
packages/
├── eslint-config/ # 공용 eslint
└── typescript-config/ # 공용 tsconfig
apps/* sind die tatsächlichen Deploy-Artefakte, packages/* enthält die von allen Apps geteilte Konfiguration und den gemeinsamen Code. Die tsconfig.json jeder App erbt einfach nur.
{ "extends": "@repo/typescript-config/nextjs.json" }
Regeln werden jetzt nur noch an einer Stelle in packages geändert.
Der turbo-Cache ist der Kern
Die eigentliche Waffe von Turborepo ist der Cache. Ändern sich die Inputs eines Tasks (Quellcode, Konfiguration) nicht, wird das vorherige Ergebnis direkt wiederverwendet.
turbo run build # 전체 빌드(바뀐 것만 실제로 돎)
turbo run build --filter=blog # blog 앱만
In turbo.json deklariert man nur die Abhängigkeiten zwischen Tasks und die Outputs.
{
"tasks": {
"build": { "dependsOn": ["^build"], "outputs": [".next/**"] }
}
}
Ab dem zweiten Build heißt es "FULL TURBO", und alles ist in wenigen Sekunden fertig.
Pro App deployen
Auch mit einem einzigen Repository bleibt das Deployment pro App getrennt. Bei Docker schneidet turbo prune nur den Teilbaum der jeweiligen App heraus und hält das Image schlank.
turbo prune blog --docker # blog + 내부 의존성만 남긴 out/ 생성
Auch dieser Blog wurde so zugeschnitten und läuft per Docker auf einem Mini-PC. Diese Geschichte erzähle ich im Beitrag zum Self-Hosting.
Fazit
apps/*= Deploy-Artefakte,packages/*= geteilte Konfiguration- tsconfig und eslint per Vererbung an einer Stelle pflegen
- Der
turbo-Cache baut unveränderte Apps nicht neu - Mit
--filterundprunepro App deployen
Wer allein mehrere Produkte betreibt, für den ist ein Monorepo kein Luxus, sondern ein Überlebenswerkzeug.
Häufige Fragen
Ab wie vielen Apps lohnt sich ein Monorepo?
Schon ab 2 Apps lohnt es sich, sobald gemeinsame Konfiguration (eslint, tsconfig, UI) entsteht. Der Kern: Einstellungen an einer Stelle ändern und alle Apps übernehmen sie.
Werden die Builds dadurch nicht langsamer?
Im Gegenteil, sie werden schneller. turbo cached die Ergebnisse von Tasks, deren Inputs sich nicht geändert haben, und baut nur die geänderten Apps neu.
Ähnliche Beiträge
- 💻 Entwicklung
Wenn ein Bucket zwei Besitzer hat, gewinnt der, der zuletzt apply ausführt
Ich fügte eine einzige S3-lifecycle-Regel hinzu, und terraform apply schlug fehl. Die Ursache: eine Struktur, in der zwei Ressourcen jeweils die lifecycle-Konfiguration desselben Buckets besitzen. S3 lifecycle arbeitet nicht auf Regel-, sondern auf Dokumentebene - der letzte Gewinner löscht die Regeln der Gegenseite stillschweigend.
- 💻 Entwicklung
Die Tests waren grün, aber es wurde nie eine Benachrichtigung verschickt
Der Code, der Jobs in die Queue legte, schlug vom ersten Tag an jedes Mal fehl. Der Fehler wurde verschluckt, und die Tests blieben dank Mocks grün. Die Geschichte, wie ein einziger Doppelpunkt drei Features lautlos getötet hat.
- 💻 Entwicklung
Der Alarm sagte, die DB sei tot. Die DB war nie tot
Innerhalb eines Tages stapelten sich vier CRITICAL-Alarme. 504, Prisma P2028, ein 500er im Admin und 'DATABASE-Dienst ausgefallen'. Ich öffnete zuerst die RDS-Metriken: 21 Stunden lang lag die CPU bei maximal 19.7%, alles gesund. Langsam war nicht die DB, sondern ein Roundtrip, der fünfundzwanzigmal über den Atlantik ging. Und den Ausfall-Alarm hatte der Health Check selbst erzeugt.