Waarom een monorepo?
Toen ik begon met het bouwen van meerdere gerelateerde websites, stond ik voor een keuze: aparte repositories of een monorepo. Na ervaring met beide benaderingen kies ik tegenwoordig bijna altijd voor een monorepo.
De voordelen
- Gedeelde code: UI componenten, configuratie en utilities op één plek
- Atomic changes: Wijzigingen die meerdere packages raken in één commit
- Consistente tooling: Eén ESLint config, één TypeScript setup, één CI pipeline
Multi-repo aanpak
- ❌Elke app een eigen repository
- ❌Duplicatie van configuratie en tooling
- ❌Versiebeheer tussen packages handmatig
- ❌CI/CD per repo apart onderhouden
- ❌Wijzigingen verspreid over meerdere PRs
Monorepo aanpak
- ✅Alle apps en packages in één repo
- ✅Gedeelde configuratie als packages
- ✅Automatische dependency resolution
- ✅Eén CI pipeline met intelligente caching
- ✅Atomic changes in één commit
De structuur
Mijn typische monorepo structuur ziet er zo uit:
├── apps/
│ ├── personal/ # Next.js app
│ ├── business/ # Next.js app
│ └── commandhub/ # Next.js app
├── packages/
│ ├── ui/ # Gedeelde componenten
│ ├── email/ # E-mail utilities
│ ├── config-tailwind/
│ ├── config-typescript/
│ └── config-eslint/
├── turbo.json
└── pnpm-workspace.yaml
pnpm Workspaces
pnpm is mijn package manager van keuze voor monorepos. Het is sneller dan npm en yarn, en de workspace support is uitstekend.
# pnpm-workspace.yaml
packages:
- "apps/*"
- "packages/*"Packages refereren naar elkaar met workspace:*:
{
"dependencies": {
"@steding/ui": "workspace:*",
"@steding/email": "workspace:*"
}
}Turborepo configuratie
De turbo.json definieert je build pipeline en caching strategie:
{
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": [".next/**", "dist/**"]
},
"dev": {
"cache": false,
"persistent": true
}
}
}Het ^build patroon zorgt ervoor dat dependencies eerst worden gebuild. De outputs array vertelt Turbo welke bestanden gecached moeten worden.
Bestand wijzigen
Je past een component aan in packages/ui
Turbo detecteert
Turborepo ziet welke packages afhankelijk zijn van de wijziging
Dependency graph
Alleen apps die packages/ui gebruiken worden gemarkeerd
Parallel build
Gemarkeerde apps worden parallel gebuild
Cache opslaan
Build output wordt gecached voor volgende keer
Tips voor de praktijk
1. Gebruik JIT compilatie voor UI packages
In plaats van je UI package te builden, kun je Next.js het direct laten transpileren:
// next.config.ts
const nextConfig = {
transpilePackages: ["@steding/ui"],
};Dit bespaart een build stap en maakt de developer experience veel sneller.
2. Gedeelde configuratie als packages
Maak aparte packages voor ESLint, TypeScript en Tailwind configuratie. Elke app extendst dan simpelweg de gedeelde config.
3. Turbo cache in CI
Turborepo's remote caching kan je CI builds dramatisch versnellen. Als alleen de personal site is veranderd, hoeft de business site niet opnieuw gebuild te worden.
Pro tip: Remote caching
Schakel Turborepo's remote cache in voor je CI/CD pipeline. Dit betekent dat builds die lokaal al gedaan zijn, niet opnieuw hoeven te draaien in CI. De besparing kan oplopen tot 85% van je totale CI-tijd.
Conclusie
Een monorepo met Turborepo en pnpm is een krachtige combinatie voor teams die meerdere gerelateerde projecten beheren. De initiële setup kost wat tijd, maar de tijdwinst op de lange termijn is enorm.