Monorepo Tools Compared: Turborepo, Nx, and Bun Workspaces
Monorepo Tools Compared: Turborepo, Nx, and Bun Workspaces
Monorepos are no longer controversial. Most teams with more than two related packages eventually consolidate into a single repository. The question isn't whether to use a monorepo -- it's which tool to manage it with.
This guide covers the five relevant options in 2026: Turborepo, Nx, Bun workspaces, pnpm workspaces, and Lerna. The right choice depends on your team size, project complexity, and tolerance for configuration.
Two Layers of Monorepo Management
Before comparing tools, separate the two layers:
- Workspace management -- resolving inter-package dependencies, hoisting shared
node_modules, linking local packages. Package managers (pnpm, Bun, yarn) handle this with theirworkspacesfeature. - Task orchestration -- running builds, tests, and lints across packages in the right order, caching results, and parallelizing where possible. Turborepo, Nx, and (historically) Lerna handle this.
Confusing these layers is the source of most monorepo frustration.
Quick Comparison
| Feature | Turborepo | Nx | Bun Workspaces | pnpm Workspaces | Lerna |
|---|---|---|---|---|---|
| Primary role | Task orchestration | Orchestration + scaffolding | Workspace management | Workspace management | Orchestration (legacy) |
| Configuration | turbo.json |
nx.json + project.json |
package.json |
pnpm-workspace.yaml |
lerna.json |
| Local caching | Yes | Yes | No | No | No |
| Remote caching | Yes (Vercel) | Yes (Nx Cloud) | No | No | No |
| Task dependencies | Yes (pipeline) | Yes (task graph) | No | --filter only |
Basic |
| Code generation | No | Yes (generators) | No | No | No |
| Learning curve | Low | Moderate-high | Minimal | Low | Low |
Turborepo
Turborepo does one thing well: it makes npm run build fast across a monorepo. It analyzes your dependency graph, runs tasks in parallel, and caches results. That simplicity is its biggest strength.
Setup and Pipelines
Add Turborepo to any existing workspace:
bun add -D turbo
The core is turbo.json, where you define task relationships:
{
"$schema": "https://turbo.build/schema.json",
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**"]
},
"test": {
"dependsOn": ["build"],
"outputs": []
},
"lint": { "outputs": [] },
"dev": { "cache": false, "persistent": true }
}
}
The ^build syntax means "run build in all dependencies first." This is how Turborepo knows to build @myorg/ui before the web app that imports it.
turbo build # build everything
turbo build --filter=web # build web and its dependencies
turbo test --filter=./packages/* # test all packages
Caching
Local caching is automatic. Run turbo build twice and the second run replays cached output in milliseconds. The cache key includes file contents, environment variables, and dependency task outputs.
Remote caching syncs across machines -- if CI already built a package with the same inputs, your local machine pulls the cached result:
turbo login && turbo link # connects to Vercel's remote cache
Self-hosted alternatives exist via custom API endpoints or community servers like turborepo-remote-cache.
When it works best: Turborepo is ideal for TypeScript monorepos with standard build/test/lint scripts where you want caching without learning a new paradigm. Configuration is one file, the learning curve is almost flat.
Nx
Nx is the most full-featured option. Where Turborepo focuses on caching, Nx provides task orchestration, code generators, dependency visualization, affected-based testing, and a plugin ecosystem. It's closer to a build system than a task runner.
Configuration
bun add -D nx @nx/js
Nx uses nx.json for global config with optional per-project project.json files:
{
"targetDefaults": {
"build": {
"dependsOn": ["^build"],
"cache": true,
"inputs": ["production", "^production"],
"outputs": ["{projectRoot}/dist"]
},
"test": { "cache": true, "inputs": ["default", "^production"] },
"lint": { "cache": true }
},
"namedInputs": {
"default": ["{projectRoot}/**/*"],
"production": ["default", "!{projectRoot}/**/*.spec.ts"]
}
}
More verbose than Turborepo, but more precise. Named inputs let you define exactly which files affect each cache key -- the production input excludes test files, so changing a test won't invalidate the build cache.
Generators and Affected Commands
Nx's killer feature is code generation. Generators scaffold new packages with consistent structure:
nx generate @nx/js:library shared-utils --directory=packages/shared-utils
nx generate @nx/react:component Button --project=ui
Nx also determines which projects are affected by a set of changes:
nx affected -t test # test only affected projects
nx affected -t build --base=main --head=HEAD
Instead of running every test on every PR, you run only tests affected by changed files. For 20+ package monorepos, this cuts CI time dramatically.
Nx Cloud adds remote caching and distributed task execution -- splitting tasks across multiple CI agents so a 30-minute build might finish in 8 minutes on four agents.
When it shines: Large monorepos (10+ packages), multiple teams, need for standardized scaffolding and CI optimization. For smaller monorepos, the overhead isn't justified.
Bun Workspaces
Bun's workspace support is the simplest option. It handles dependency resolution and local package linking -- nothing more.
Setup
{
"name": "my-monorepo",
"private": true,
"workspaces": ["apps/*", "packages/*"]
}
Run bun install and Bun links all local packages. Import @myorg/shared from your app and it resolves to the local source.
Running Tasks and Limitations
bun run --filter '*' build # run build in all packages
bun run --filter './apps/*' dev # run dev in all apps
bun run --filter 'web' test # run test in "web"
There's no task dependency ordering. If web depends on ui and both have a build script, Bun might build web before ui finishes. For projects where packages consume each other's build output, you need Turborepo on top.
This limitation vanishes if you use Bun's native TypeScript execution -- where packages import each other's source directly. If web imports @myorg/ui/src/index.ts rather than @myorg/ui/dist, there's nothing to build in dependency order.
When it works best: Small monorepos (2-5 packages) running TypeScript directly, where you want minimal tooling. Many projects start here and add Turborepo when caching becomes valuable.
pnpm Workspaces
pnpm is the most popular package manager for monorepos in the Node.js ecosystem, and for good reason.
Setup and Why It's Popular
# pnpm-workspace.yaml
packages:
- "apps/*"
- "packages/*"
pnpm's strict dependency resolution is its main advantage. Unlike npm and yarn, pnpm doesn't hoist all dependencies to the root. Each package only accesses its declared dependencies, catching missing declarations that silently work elsewhere but fail in production.
pnpm add zod --filter web # scoped install
pnpm -r run build # run build in all packages
pnpm -r --filter '...[origin/main]' run test # test changed packages
pnpm also deduplicates packages with a content-addressable store. Ten monorepos using React 19 share one copy on disk.
pnpm + Turborepo
The most common monorepo setup in 2026 is pnpm for workspaces and Turborepo for orchestration. Combine the workspace config with a turbo.json:
{
"tasks": {
"build": { "dependsOn": ["^build"], "outputs": ["dist/**"] },
"test": { "outputs": [] },
"lint": { "outputs": [] }
}
}
This gives you strict dependency resolution, disk efficiency, caching, and parallel execution. It's the pragmatic default for most teams.
Lerna
Lerna was the original JavaScript monorepo tool. In 2022, Nrwl (the Nx company) took over maintenance and rebuilt it on Nx's task runner.
Lerna is not dead -- it's maintained and functional. But its role has narrowed to publishing. lerna publish and lerna version handle version bumping, changelog generation, and npm publishing across interdependent packages:
lerna version --conventional-commits
lerna publish from-git
If you have an existing Lerna monorepo, there's no urgency to migrate. If starting fresh, use Turborepo or Nx for orchestration and changesets (@changesets/cli) for publishing.
Choosing the Right Tool
By Project Size
Solo/small, 2-3 packages: Bun or pnpm workspaces alone. Add tooling when you feel the pain.
Small team, 3-8 packages: pnpm + Turborepo. Caching and parallel builds without heavy config. Setup takes 15 minutes.
Large team, 10+ packages: Nx. Generators, affected commands, and distributed CI justify the learning curve.
Open source multi-package library: pnpm + Turborepo + changesets for publishing.
By Runtime
Bun-native: Bun workspaces, optionally with Turborepo. If you run TypeScript directly without build steps, workspace linking is all you need.
Node.js: pnpm + Turborepo. pnpm's strictness catches real bugs, Turborepo's caching saves real time.
Mixed (Go + TypeScript, Python + TypeScript): Nx. Its plugin system handles non-JavaScript projects and cross-language task dependencies.
Common Pitfalls
Internal Packages Without Build Steps
The simplest monorepo pattern exports TypeScript source directly:
{
"name": "@myorg/shared",
"exports": { ".": "./src/index.ts" }
}
Apps handle compilation through their own bundler or runtime. No build step, no ordering, no caching needed. This works natively with Bun and with bundlers like Vite.
Cache Invalidation
If your build depends on something outside the file system -- an API URL, database schema, environment config -- declare it or the cache serves stale results:
{
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"],
"env": ["API_URL", "DATABASE_URL"]
}
}
}
Over-Engineering Early
The biggest monorepo mistake is adopting heavy tooling prematurely. A team of three with four packages does not need distributed task execution. Start with workspaces. Add Turborepo when builds get slow. Add Nx when you need generators or affected commands.
Recommendations
- Default choice: pnpm + Turborepo. Handles 90% of monorepo needs with minimal configuration.
- Bun projects: Bun workspaces, add Turborepo when you need caching.
- Enterprise: Nx with Nx Cloud. The upfront investment pays off at scale.
- Publishing: Add changesets regardless of orchestration tool.
- Lerna: No rush to migrate, but don't start new projects with it.
The monorepo tooling space has matured. Pick the tool that matches your current project size, not the size you hope to reach in two years, and move on to building your actual product.