Laden...
Laden...
The WPDeveloper plugin suite — BetterDocs, NotificationX, SchedulePress, BetterLinks, and a few smaller ones — was a monorepo that took close to 8 minutes for a clean build. After Turborepo with remote caching, the same build dropped to ~40 seconds on warm caches. Notes on what actually moved the needle.
The WPDeveloper plugin suite — BetterDocs, NotificationX, SchedulePress, BetterLinks, and a few smaller ones — shared a monorepo with shared UI components, shared TypeScript types, and shared tooling. Clean builds were taking close to 8 minutes by the time we had ~12 plugins in there, which meant CI was the bottleneck on PR feedback. Adding Turborepo with remote caching dropped warm-cache builds to about 40 seconds. That changed how the team worked — PRs got reviewed faster, hotfixes went out faster, and we stopped scheduling builds around lunch breaks.
Monorepos promise shared code, unified tooling, and atomic changes across packages. They deliver — until the repo grows large enough that builds take 10 minutes and CI costs spiral. Turborepo is a build orchestrator designed to keep monorepos fast as they scale, using aggressive caching and dependency-aware task graphs.
Turborepo caches the output of every task keyed on the task inputs — source files, dependencies, and config. If nothing relevant has changed, the task is skipped entirely and its cached output is restored. This turns most CI runs into glorified cache restores.
// turbo.json
{
"$schema": "https://turbo.build/schema.json",
"tasks": {
"build": {
"dependsOn": ["^build"],
"inputs": ["src/**", "package.json", "tsconfig.json"],
"outputs": ["dist/**", ".next/**"]
},
"test": {
"dependsOn": ["build"],
"inputs": ["src/**", "tests/**"],
"outputs": ["coverage/**"]
},
"lint": {
"inputs": ["src/**", ".eslintrc*"]
},
"dev": {
"cache": false,
"persistent": true
}
}
}Info
^build means "the build task of this package's dependencies must run first". This dependency graph is what lets Turborepo parallelize tasks across packages while respecting order.
Local caching helps a single developer. Remote caching helps an entire team. When one engineer builds a package, the cache uploads to a shared store. Every other engineer — and every CI run — can restore that cache instead of rebuilding. Cold CI runs become warm.
# Link your repo to Vercel for free remote caching
npx turbo login
npx turbo link
# Or self-host with the open-source server
# https://turbo.build/docs/cache/self-hosted
# Run with remote cache enabled
turbo run buildTurborepo can run tasks only for packages affected by your changes. Combine filters with git to build exactly what changed in a PR, not the entire repo.
# Build only the web package
turbo run build --filter=web
# Build web and everything it depends on
turbo run build --filter=web...
# Build everything that depends on the ui package
turbo run build --filter=...ui
# Build only packages changed since main
turbo run build --filter=[origin/main]
# In CI — build everything affected by the PR
turbo run build test --filter=[HEAD^1]The de-facto standard for Turborepo monorepos is an apps/ directory for runnable projects and a packages/ directory for shared code. This maps cleanly to most workflows and keeps the task graph interpretable.
my-monorepo/
├── apps/
│ ├── web/ # Next.js app
│ ├── docs/ # Astro docs site
│ └── mobile/ # React Native app
├── packages/
│ ├── ui/ # Shared component library
│ ├── config-eslint/ # Shared ESLint config
│ ├── config-tsconfig/
│ └── core/ # Shared business logic
├── turbo.json
├── package.json
└── pnpm-workspace.yamlTurborepo works with any workspace-aware package manager, but pnpm has become the consensus choice. It installs faster, uses less disk, and its strict node_modules layout catches phantom dependencies that npm hoisting would hide.
Tip
Use pnpm workspaces with a shared tsconfig.base.json and centralized ESLint config. Each package extends the base. This keeps per-package config minimal and upgrades affect the whole repo in one commit.
For a medium monorepo (20 packages, 100k lines), typical numbers after adopting Turborepo with remote caching: CI build time drops from 8 minutes to 90 seconds on cache hits. Local turbo run test goes from 45 seconds to instant when you have not touched anything. The compounding effect on developer productivity is dramatic.
On the WPDeveloper plugin monorepo, getting the Turborepo config right took a couple of iterations — we had a few packages with sloppy "inputs" declarations that were invalidating caches more than necessary. Once we tightened those up, the cache hit rate went above 80% on regular CI runs. The key insight is that the cache is only as good as your config: be honest about what each task actually depends on, and the speedup is so significant it feels like cheating. After this migration I reach for Turborepo on every monorepo I touch.
Meer in Tooling
Spent a weekend rebuilding the static parts of sapan.dev in Astro to see what the islands model actually feels like. TTI dropped from ~600ms to ~150ms, the JS bundle went from 240KB to 11KB — and there were a few sharp edges that made me appreciate what Next.js handles for free.
Migrated sapan.dev from ESLint + Prettier to Biome over a weekend — pre-commit hooks went from ~12s to under a second, and the config dropped from three files to one. Notes on what migrated cleanly and what I had to keep ESLint for.
The xCloud v1 frontend runs on Vue 3 + Vite + a Laravel API — the Vite dev server is what makes that loop tolerable. Notes on what Vite 6 ships, where Rolldown changes things, and the few cases where I still hit friction.