2026-06-28 · 7 min
Monorepo: Turborepo vs Bun Workspaces 8 Bulan
Delapan bulan lalu, monorepo SaaS klien Karawaci punya 14 package (5 app Next.js, 3 service Bun, 4 shared library, 2 internal CLI). Kami pakai Turborepo + npm workspaces. Cold install 4 menit 20 detik di CI, dan tim 6 engineer mulai mengeluh tiap branch baru.
Hari ini, kami pakai Bun Workspaces (bun install + custom orchestration). Saya share angka, dan kenapa saya tetap pertahankan Turborepo untuk satu use case.
Setup awal
- Repo: 14 package, 78k LOC TypeScript, 12k LOC TSX.
- Dependencies total: 1.247 di lockfile (deduped), ~287 unique top-level.
- CI: GitHub Actions, runner ubuntu-latest 4 vCPU 16GB.
- Tim: 6 engineer (3 Tangerang, 2 Jakarta, 1 Bandung).
- Deploy cadence: 5-8 deploy/hari ke staging, 2-4 ke production.
Baseline Turborepo + npm:
- Cold install (no cache): 4 menit 20 detik
- Warm install (lockfile sama): 1 menit 18 detik
- Full build (turbo run build, no cache): 6 menit 45 detik
- Full build (turbo cache hit): 22 detik
- Remote cache hit ratio: 71%
Kenapa migrate
Tiga keluhan tim:
- Cold install lambat: branch baru = 4+ menit nunggu CI. Untuk PR review-driven workflow yang kami pakai (push → CI run → review), ini bottleneck.
node_modulessize: 2.8GB. Disk pressure di laptop dev MacBook M2 256GB.- TypeScript transpilation di shared lib: tsc watch mode lambat, ~12 detik per change di shared package.
Saya sudah jalan dengan Bun di payment gateway production selama 4 bulan, jadi confidence-nya ada.
Migrasi: 3 hari, 1 weekend, 4 PR
Hari 1: ganti package manager
// package.json root
{
"workspaces": ["apps/*", "services/*", "packages/*", "tools/*"],
"packageManager": "bun@1.3.12"
}
Hapus package-lock.json, jalankan bun install.
Cold install: 38 detik. Dari 4m20s. Sekitar 87% reduction.
Sekitar 80% dari speed-up itu adalah Bun’s parallel network fetch + native install (vs npm yang single-threaded di banyak phase). Sisa 20% dari Bun’s lockfile format (binary, faster to parse).
Hari 2: orchestration replacement
Turborepo kasih kami:
- Task pipeline (
builddepends on^build) - Remote cache (kami pakai Vercel remote cache, $20/mo)
- Filtered task runs (
turbo run test --filter=...[origin/main])
Bun Workspaces tidak punya equivalent built-in. Saya tulis scripts/orchestrate.ts:
import { execSync } from 'node:child_process';
import { readdirSync, readFileSync } from 'node:fs';
interface Workspace {
name: string;
path: string;
dependencies: string[];
}
function loadWorkspaces(): Workspace[] {
const root = JSON.parse(readFileSync('package.json', 'utf-8'));
const patterns = root.workspaces as string[];
// ... glob expand, read each package.json
}
function topologicalSort(workspaces: Workspace[]): Workspace[] {
// Kahn's algorithm, dependency order
}
function runTask(task: string, affected: string[]) {
const sorted = topologicalSort(loadWorkspaces())
.filter(w => affected.includes(w.name));
for (const ws of sorted) {
console.log(`[${ws.name}] ${task}`);
execSync(`bun run ${task}`, { cwd: ws.path, stdio: 'inherit' });
}
}
180 LOC, no external deps. Tidak punya remote cache, tapi punya local content-hash cache yang saya simpan di .bun-cache/.
Hari 3: CI integration
GitHub Actions config:
- uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.12
- uses: actions/cache@v4
with:
path: |
~/.bun/install/cache
.bun-cache
key: bun-${{ runner.os }}-${{ hashFiles('bun.lockb') }}
restore-keys: bun-${{ runner.os }}-
- run: bun install --frozen-lockfile
- run: bun run scripts/orchestrate.ts build --affected
--affected detect changed packages via git diff vs main, build hanya yang impacted plus dependents.
Hasil 8 bulan
| Metric | Turborepo + npm | Bun Workspaces | Delta |
|---|---|---|---|
| Cold install CI | 4m 20s | 38s | -85% |
| Warm install CI | 1m 18s | 6s | -92% |
| Full build (no cache) | 6m 45s | 5m 12s | -23% |
| Affected build (avg) | 1m 8s | 42s | -38% |
| Cache hit ratio | 71% (remote) | 88% (local) | +24% |
| node_modules size | 2.8 GB | 1.4 GB | -50% |
| CI cost (GitHub minutes) | $87/mo | $34/mo | -61% |
Cache hit ratio naik karena content-hashing saya per-package lebih granular dari Turborepo default. Turborepo hash include semua deps transitif yang sometimes overinvalidate.
Yang break
1. sharp native module
Image processing pakai sharp. Bun install download prebuilt binary tapi binary itu Node-ABI specific. Crash di runtime:
Error: Could not load the "sharp" module using the linux-x64 runtime
Fix: rebuild eksplisit per workspace yang pakai sharp:
{
"scripts": {
"postinstall": "bun pm trust --all"
}
}
Plus pin sharp ke versi yang punya Bun-compatible prebuilt (v0.34+).
2. TypeScript path mapping di Vite
Vite 5 kami pakai untuk salah satu app. tsconfig.json paths:
{
"paths": {
"@kami/ui": ["../packages/ui/src/index.ts"]
}
}
Bun resolve correctly, tapi Vite (via vite-tsconfig-paths plugin) kadang miss-resolve karena symlink structure Bun beda. Symptom: dev server kasih Failed to resolve import random.
Fix: explicit alias di vite.config.ts:
resolve: {
alias: {
'@kami/ui': fileURLToPath(new URL('../../packages/ui/src/index.ts', import.meta.url))
}
}
Ugly, tapi works.
3. peerDependencies warning hell
Turborepo lebih permissive untuk peer dep mismatch. Bun strict, kasih warning verbose di tiap install. Saya silence di CI dengan --silent flag dan log warning ke artifact untuk audit weekly.
4. No remote cache
Local cache saja tidak cukup saat 6 engineer parallel push branch. Engineer A bangun shared lib version X, engineer B push 2 menit kemudian dengan dep yang sama — B rebuild dari nol. Turborepo remote cache mengatasi ini.
Workaround saya: self-host Turborepo remote cache server (open source: turbo-remote-cache) di VPS Hetzner kecil (€4/mo). Saya kombinasikan: Bun untuk install, Turborepo cache server untuk build artifact sharing.
Yes, saya combine keduanya. Tidak ideal, tapi practical.
Kapan saya tetap rekomendasi Turborepo
- Tim > 10 engineer dengan high branch concurrency
- Butuh visualisasi task graph (Turborepo punya
--graphyang output Mermaid) - Heavy reliance di remote cache dari Vercel (jika sudah subscribe Vercel Pro)
- Mixed package manager (Turbo support pnpm, yarn, npm sekaligus)
Kapan Bun Workspaces cukup
- Tim < 8 engineer
- Monorepo < 20 package
- Mayoritas TypeScript / JavaScript (tanpa heavy native module)
- Cost-conscious (CI minute matters)
Verdict
Bun Workspaces untuk monorepo SaaS Indonesia ukuran SMB-mid: pilihan yang menghemat CI time dan disk. Untuk monorepo ukuran enterprise dengan tim 15+ engineer dan remote cache mandatory: Turborepo masih unggul.
Saya tidak akan migrate lagi seandainya monorepo ini punya 5 engineer atau kurang — overhead 3 hari migration tidak worth untuk team kecil yang sudah comfortable. Tapi untuk tim 6 yang complain tiap minggu: ROI tercapai dalam 3 minggu (CI cost saved alone bayar engineering time).
Untuk konteks deployment, lihat juga catatan saya di Cloudflare Pages vs Vercel.
Ditulis oleh Reza Pradipta