← All articles
LANGUAGES Monorepo Tools Compared: Turborepo, Nx, and Bun Work... 2026-02-09 · 7 min read · monorepo · turborepo · nx

Monorepo Tools Compared: Turborepo, Nx, and Bun Workspaces

Languages 2026-02-09 · 7 min read monorepo turborepo nx bun pnpm 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:

  1. Workspace management -- resolving inter-package dependencies, hoisting shared node_modules, linking local packages. Package managers (pnpm, Bun, yarn) handle this with their workspaces feature.
  2. 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

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.