Best monorepo tool in 2026 — pnpm + Turborepo or Nx?
A direct answer to the choice most TypeScript teams delay for months. When pnpm + Turborepo is the right call and when Nx earns its setup cost.
By Ethan
1,848 words · 10 min read
If you have ≤5 developers and a simple monorepo task graph, use pnpm + Turborepo. Setup takes 15 minutes, the cache is free via Vercel, and it works without restructuring anything you already have.
If you have 6+ developers, complex CI, or plans to grow, use pnpm + Nx. The extra 2–3 hours of setup pays back within a week through affected detection alone. For the 80% case — mid-size TypeScript team on GitHub Actions — Nx edges ahead once you’re past ~5 people.
Who this is for
TypeScript teams on a monorepo (greenfield or evaluating migration) running CI on GitHub Actions. If you’re a solo developer with three packages, pick Turborepo — you don’t need the rest of this article.
What we tested
- pnpm workspaces v10 — required underlying layer for both tools
- Turborepo 2.7 (December 2025) — MIT license, Rust core
- Nx 21 (2025) — MIT license, TypeScript + Rust hybrid
Benchmark data comes from two sources:
- Navanath’s migration experiment — a real 12-package repo (3 Next.js apps, 4 React libs, 2 Node APIs, 3 config packages), 8 engineers, GitHub Actions with pnpm. Machine specs not disclosed — caveat noted below.
- vsavkin/large-monorepo — M2Max MacBook Pro, February 2025, ~26,000 synthetic components. Maintained by Victor Savkin, Nx co-creator. We use this data directionally only.
When a benchmark has provenance problems, we say so.
Findings
Setup time
Turborepo’s pitch is accurate: you can add it to an existing pnpm workspace in 15 minutes. Add one turbo.json file, run two commands for remote caching, done. No restructuring required.
The v2.x config renamed pipeline to tasks — every pre-2024 tutorial gets this wrong:
{
"$schema": "https://turborepo.dev/schema.json",
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**"],
"inputs": ["src/**", "package.json"]
},
"test": {
"dependsOn": ["build"],
"cache": true
}
},
"envMode": "strict"
}
dependsOn: ["^build"] runs build in all dependencies first (topological). envMode: "strict" means only explicitly declared env vars affect the cache key — the safe default in v2.
Nx takes longer. Navanath’s migration experiment logged ~2.5 hours for a 12-package repo: updating path references, rewriting build scripts, restructuring what the generator expects. The minimal nx.json is 3 lines, but minimal Nx skips most of what makes Nx worth adopting.
The calculus flips later: Turborepo users eventually build their own scaffolding scripts, version-consistency checks, and migration tooling. Nx’s generators handle those from day one. The break-even is roughly when your third or fourth developer joins and starts creating new packages regularly.
If you’ve decided on Turborepo, see our step-by-step pnpm + Turborepo setup guide to bootstrap the monorepo correctly from the start.
Remote caching
Turborepo’s remote cache is free via Vercel on all plans, including the free tier. Setup:
npx turbo login
npx turbo link
If you’d rather self-host, the cache API is open and well-documented. Community implementations support S3, Cloudflare R2, GCS, and Azure Blob. Turborepo 2.5 published a human-friendly viewer for its existing cache API spec, which makes evaluating self-hosting options easier.
Does remote caching actually help? Yes, measurably. Mercari Engineering published a case study on their self-hosted Turborepo remote cache and reported approximately 50% shorter Turbo task durations and 30% shorter total job duration on a large application build.
Nx Cloud’s free tier gives 50,000 credits per month — enough for most small-to-mid teams. Past that, the Team plan runs $19/contributor plus credits and a per-concurrent-CI-connection fee. The Team plan includes Manual DTE (static task assignment). Dynamic agent allocation — where agents pull tasks based on real-time CPU/RAM availability — is Enterprise-only with custom pricing.
The practical difference: Turborepo remote caching costs nothing at any team size. Nx Cloud is free until you grow into the features that justify the cost — and those features are real, which is the point of the next section.
Affected detection
This is where the tools diverge most for everyday CI.
On Navanath’s 12-package repo (no machine spec):
| Scenario | Turborepo | Nx |
|---|---|---|
| Cold cache build | 2m 47s | 2m 31s |
| Warm cache build | 387ms | 421ms |
| After single-line change | 48s | 43s |
| CI with affected detection | ~3–4 min | ~1–2 min |
Warm cache is a wash — both tools are fast. Cold cache has a modest Nx edge. The real gap is in CI: Nx analyzes the project graph to determine which packages a PR actually touches and runs only those. On a 12-package repo, that typically means running 3–4 packages per PR instead of all 12, cutting CI from ~14 minutes to ~1–2 minutes.
Turborepo has no native affected detection as of v2.7. You can script around this with git diff + --filter, but you’re rebuilding something Nx includes.
The vsavkin benchmark shows Nx at 192ms vs Turborepo at 1,432ms — a 7.5× difference on a synthetic 26,000-component repo. The architectural reason is real: Nx’s daemon pre-computes the project graph and caches file hashes in .nx/cache; subsequent runs skip re-hashing already-seen files. Turborepo’s daemon is less aggressive here. But this benchmark is maintained by Nx’s co-creator on a repo designed to stress-test this exact advantage. Real-world numbers are smaller. Use vsavkin directionally, not as a headline.
Distributed CI
For single-machine CI, the difference is modest. Nx’s own benchmark shows ~16% faster for a comparable setup — biased provenance, directionally plausible.
The more interesting gap is in distributed CI. Turborepo bins tasks upfront: before execution starts, each agent receives a batch of tasks. If one batch has a fat task, that agent runs for 18 minutes while the others finish in 3. Nx Cloud Enterprise’s dynamic task execution feeds tasks based on current CPU and RAM availability — when an agent finishes, it pulls the next available task. No idle agents. (The Team plan includes Manual DTE, which assigns agent batches statically.)
Nx’s 4-machine CI benchmark: 19m 18s (Turborepo) vs 9m 20s (Nx). That benchmark is self-published and the gap is likely overstated. The architecture difference is real, though, and it compounds as you add machines.
If CI is your bottleneck and you’re running multiple agents, Nx’s dynamic scheduling matters. On a single machine or two, it doesn’t.
Nx generators and lock-in
nx g @nx/next:app my-app scaffolds a full Next.js project: TypeScript config, Jest setup, ESLint rules, package.json — all correct and consistent with every other package in the repo. nx migrate latest updates all plugin configs across every package when you upgrade.
If code generation and automated migrations are regular workflows, these are genuinely useful. If they’re not, they’re overhead.
The lock-in: Nx executors replace direct tool invocations. @nx/webpack:webpack in your targets means switching away requires rewriting targets, not just changing a dependency. Plugin versions couple to Nx core — major upgrades need coordinated bumps across all plugins. Debugging Nx-specific cache misses is harder because the plugin layer adds indirection.
Start with Nx if you’re greenfield and want opinionated scaffolding. Avoid it if you have a carefully tuned build setup you don’t want touched by generators.
pnpm catalog — adopt this regardless of which task runner you pick
pnpm 9.5 introduced catalogs; pnpm 10.x stabilized them. This is worth calling out separately because it’s a genuine quality-of-life improvement that works with Turborepo, Nx, or nothing.
Before catalogs, keeping react: "^18.3.1" consistent across 20 packages in a monorepo required a script or discipline. With catalogs, it’s one line in pnpm-workspace.yaml:
catalog:
react: ^18.3.1
typescript: ^5.4.0
And in each package.json:
{
"dependencies": {
"react": "catalog:"
}
}
Upgrading React across the entire monorepo is one line changed in one file. On publish, catalog: resolves to the pinned version — transparent to npm’s registry.
pnpm 10.x added catalogMode to enforce catalog usage and cleanupUnusedCatalogs to prune stale entries. If you’re on pnpm 9.5 or later, use catalogs. Opt-in, backward-compatible, removes a real annoyance.
What about Lerna?
No, not for new projects.
Lerna’s current niche is coordinated versioning and npm publishing. lerna version + lerna publish remains the best-in-class tool for that specific workflow. Lerna 9 (September 2025) added OIDC Trusted Publishing, which eliminates static npm tokens in CI — that’s a real improvement if you’re publishing packages.
For task running and caching, Lerna is behind both Turborepo and Nx. Don’t start a new monorepo with it unless coordinated npm publishing is your primary requirement and changesets or nx release don’t fit.
Verdict
Use pnpm + Turborepo if:
- You have ≤5 developers
- Your task graph is simple (build → test → lint, no deep interdependencies)
- You want to add caching to an existing workspace without restructuring anything
- You’re already on Vercel or fine with a free Vercel account for remote caching
Use pnpm + Nx if:
- You have 6+ developers
- CI time is a bottleneck and you want affected detection without scripting it yourself
- You want generators for scaffolding and
nx migratefor upgrades - You’re on a growth path — Nx’s overhead amortizes faster as the team scales
For the 80% case — mid-size TypeScript team on GitHub Actions, greenfield or early-stage migration — Nx edges ahead past ~5 people, primarily because of affected detection. The CI time savings from running 3 packages per PR instead of 12 justify the setup cost within a week of regular development.
Caveats
Nx lock-in is real. Nx executors and generators restructure your build config in ways that take hours to undo. Turborepo is easier to adopt and easier to remove.
Nx DTE pricing is opaque. The credit system is hard to model without knowing your team’s exact task profile. The 50k/month free tier covers many small-to-mid teams, but full dynamic task feeding requires Enterprise. Don’t commit to Nx Cloud DTE without estimating your monthly credit burn first.
Turborepo has no native affected detection. On a large repo with many independent packages, this compounds. You can script around it, but you’re building something Nx includes. For a full account of what silently accumulates after setup, see Turborepo monorepo pitfalls we learned the hard way.
Benchmark provenance. The vsavkin benchmark is maintained by Nx’s co-creator on a repo optimized for Nx’s daemon and disk-cache advantages — use it directionally. Navanath’s numbers come from a real 12-package migration but lack machine specs. Neither is neutral. Treat the numbers as indicators, not verdicts.
Vercel affiliate link above. See the disclosure at the top of this article. The Turborepo remote cache recommendation would be the same without it — it’s free on any Vercel account, including the free tier, and the self-hosted option is also viable.
References
- Turborepo remote caching docs
- turbo.json v2.x configuration reference
- Turborepo 2.0 release notes —
taskskey, MIT license - Turborepo 2.7 release notes — devtools, composable config
- Nx vs Turborepo (Nx-authored)
- Nx Cloud pricing
- Nx 2026 roadmap
- vsavkin/large-monorepo benchmark — Nx co-creator’s benchmark; M2Max, Feb 2025
- Navanath migration experiment — 12-package real repo, no machine spec
- pnpm catalogs
- pnpm workspaces
- Mercari Engineering — Turborepo remote cache case study