Turborepo (now owned by Vercel) became the default monorepo tool for TypeScript/JavaScript projects in 2024 and consolidated that lead through 2026. The combination of a smart task graph and aggressive caching means you can run turbo build across 20 packages and only rebuild what changed. Here's how to set up a real monorepo, not a Hello World.
What changed in 2026
- Turborepo 3 released stable with major caching improvements and a cleaner config syntax.
- Remote caching free tier generous enough for most indie projects (2GB).
- Pnpm 9 is the default package manager in templates — faster, less disk usage than npm/yarn.
The repo structure
my-monorepo/
apps/
web/ # Next.js app
docs/ # Astro docs site
api/ # Cloudflare Worker
packages/
ui/ # React component library
db/ # Drizzle schema + queries
config/ # ESLint, TS, Prettier shared config
types/ # Shared TS types
turbo.json
pnpm-workspace.yaml
package.json
Apps are leaves. Packages are shared and depend on each other but are not deployed. The split avoids accidentally importing dev tooling into runtime code.
The setup
pnpm dlx create-turbo@latest my-monorepo
cd my-monorepo
Then in pnpm-workspace.yaml:
packages:
- "apps/*"
- "packages/*"
In turbo.json:
{
"$schema": "https://turbo.build/schema.json",
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": [".next/**", "dist/**"],
"env": ["NODE_ENV"]
},
"lint": {},
"test": { "dependsOn": ["^build"] },
"dev": { "cache": false, "persistent": true }
}
}
^build means "build my dependencies first". cache: false for dev avoids stale state.
Sharing packages
In packages/ui/package.json:
{
"name": "@my/ui",
"exports": { ".": "./src/index.ts" },
"scripts": {
"build": "tsup src/index.ts --format esm,cjs --dts",
"dev": "tsup src/index.ts --format esm --watch"
}
}
In apps/web/package.json:
{
"dependencies": { "@my/ui": "workspace:*" }
}
workspace:* tells pnpm "use the local workspace package, not npm". The tsup build emits ESM + CJS + types. Apps import as if it's an npm package.
Comparison: Turborepo vs alternatives
| Tool |
Best for |
Skip if |
| Turborepo 3 |
TS/JS monorepos, Vercel-hosted |
Polyglot needs |
| Nx |
Full-featured, Angular-heavy shops |
You want lean |
| Lerna |
Legacy, package publishing |
Building apps |
| Yarn workspaces alone |
Tiny monorepos, no task orchestration |
Anything with build steps |
| Pnpm workspaces alone |
Same |
Same |
| Bazel |
Massive scale, polyglot |
Most teams |
Remote caching
The killer feature for teams. Turbo writes build artifacts to a shared cache. CI runs that fetch a cached build complete in seconds instead of minutes. Setup:
turbo login
turbo link
Now CI shares the cache. A 5-minute build that already ran for someone else takes 5 seconds. For teams with even modest CI bills, this saves real money.
Common mistakes to avoid
Putting too much in shared packages. Every shared package is a build dependency for downstream. Resist.
Forgetting outputs in turbo.json. Tasks without declared outputs don't get cached.
Using tsc for both apps and packages. Apps should use their bundler; packages use tsup or unbuild to emit ESM+CJS+DTS.
Skipping CI cache configuration. Without remote caching, monorepo CI is slower than separate repos.
Mixing package managers. Pick one (pnpm) and don't deviate.
FAQ
Can I migrate an existing repo to Turborepo?
Yes. Most painful part is splitting code into packages. Start with apps/ and add packages/ as patterns emerge.
Does Turborepo work outside Vercel?
Yes — works fine on Cloudflare Pages, AWS, self-hosted CI. Remote caching works against any S3-compatible store.
What about Bun's monorepo support?
Bun handles workspaces fine. Combine with Turborepo for the task graph + caching layer.
Are there licensing concerns?
Turborepo is MIT. Remote caching service has free + paid tiers; self-host alternatives exist.
Where to go next
For related guides see Bun vs Deno vs Node in 2026, TypeScript strict mode guide for 2026, and Vitest vs Jest in 2026.