Comparison

Turborepo, Nx, or no build tool at all?

The verdict

Start with plain pnpm workspaces and no task runner at all. A monorepo is a package manager feature, not a product you have to buy into, and for five packages a bare pnpm -r build is genuinely fine. Add Turborepo the day a full build gets annoying: it is a Rust task runner that reads your existing workspace config, needs one turbo.json file, and caches task outputs locally and remotely. Reach for Nx when you need what Turborepo deliberately does not do - code generation, enforced module boundaries, non-JavaScript languages in the same repo, or distributed task execution across CI machines. The failure mode to avoid is adopting Nx on day one for a four-package repo and spending your first sprint on plugin configuration.

All three approaches solve the same core problem: running tasks across many packages without rebuilding things that did not change. They differ enormously in how much machinery you take on to get there, and the right answer moves as the repository grows. Treat this as a progression rather than a one-time choice.

pnpm workspaces vs Turborepo vs Nx

Note that these are layered, not mutually exclusive. Turborepo and Nx both sit on top of a package manager's workspaces; neither replaces pnpm.

Dimension pnpm workspaces Turborepo Nx
Setup cost One pnpm-workspace.yaml. You already have it if you use pnpm. One turbo.json describing task dependencies and outputs. An afternoon at most. Project config, plugins per technology, and a mental model to learn. Days, not hours.
Task caching None. Every run does the work again, which is the whole reason to graduate. Content-hash based local and remote cache, skipping any task whose inputs are unchanged. Same idea, with finer-grained named inputs so a README edit does not invalidate a build.
Changed-package selection Yes, via filters against a git ref. Underrated and often enough on its own. Filters plus the cache, so unchanged work is skipped even when it is selected. The affected-projects graph, which is the most sophisticated of the three.
Distributed CI Whatever you build with a job matrix yourself. Not built in. A remote cache shared across machines is the main lever. Nx Agents distribute individual tasks across machines automatically. The real differentiator at scale.
Languages JavaScript and TypeScript only. Language agnostic in principle, JavaScript and TypeScript in practice. Genuinely polyglot, with first-party plugins now covering Maven and .NET alongside the JavaScript stack.
Governance and code generation None. Conventions live in a README and erode. None by design. Turborepo runs tasks and stops there. Generators for new packages plus lint rules that enforce which projects may import which. Worth real money on a large team.
Cost at real usage Free. Free, including remote caching on Vercel. Self-hosting the cache elsewhere is a small open source service. Free tier, then Nx Cloud per contributor per month for remote cache and agents. Usually pays back in CI minutes at scale.
Exit cost Zero. There is nothing to leave. Low. Delete turbo.json, change scripts back, and your packages are untouched. Highest of the three. Generators, executors, and plugin conventions reach into how projects are structured.

When each option is the right call

Choose pnpm workspaces alone when

  • You have fewer than about ten packages and a full build finishes in under a minute.
  • The repo is one app plus a handful of shared libraries, which describes most product teams.
  • You want zero additional concepts for a new hire to learn before they can ship.
  • Your CI already caches the store and you are not actually waiting on builds.
  • You would rather add a tool when you feel the pain than pre-empt pain you may never feel.

Choose Turborepo when

  • Builds have crossed the annoying threshold and cache hits would eliminate most of the wait.
  • The repo is JavaScript and TypeScript, roughly under a hundred packages.
  • You deploy on Vercel, where remote caching is free and needs no configuration.
  • You want the tool to be removable. A task runner you can delete in ten minutes is a safe bet.
  • Your team wants faster CI, not an opinion about how the repo should be organised.

Choose Nx when

  • The repo is polyglot. A Java or .NET service alongside the frontend is the clearest signal, and nothing else here handles it.
  • Fifty or more engineers commit to the same repo, where distributing tasks across CI agents beats caching alone.
  • You need enforced architectural boundaries so a feature package cannot quietly import from another team's internals.
  • New packages should be generated from a template rather than copy-pasted, so conventions hold as the repo grows.
  • You want a dependency graph you can show a stakeholder, and CI that can retry and self-heal known-flaky steps.

What actually makes a monorepo fast

At equal cache hit rates, Turborepo and Nx produce similar build times. That is the finding to internalise before you spend a week comparing them, because it means the tool is not the variable - your cache hit rate is. A repository where every pull request touches a shared config file, or where task inputs are declared so loosely that any change busts everything, will be slow under either tool. Getting inputs and outputs declared precisely is the work; picking the runner is the easy part.

The genuine architectural difference shows up past a certain size. Turborepo parallelises across the cores of one machine, so a CI run is bounded by the largest single machine you are willing to rent. Nx Agents split individual tasks across many machines and reassemble the results, which changes the shape of the curve rather than its constant factor. If a full CI run takes forty minutes on the biggest runner available, that difference is worth paying for. If it takes four, it is not.

Do not skip the no-tool baseline. pnpm can already filter by package, follow dependency topology, and select only packages changed since a git ref. For a repo with an app, a UI library, and a config package, that plus a CI cache of the pnpm store gets you most of the benefit with nothing to learn and nothing to maintain. Plenty of teams add a task runner because monorepos are supposed to have one, then discover their build was never the bottleneck.

Watch the direction of travel too. Nx has been pushing into CI intelligence - self-healing runs, a graph view built for thousands of projects, first-party plugins for Maven and .NET - which continues its move from "JavaScript monorepo tool" toward "enterprise build platform". Turborepo has stayed narrow on purpose and keeps investing in being fast and boring. Those are coherent, opposite strategies, and which one you find appealing is a decent proxy for which tool you should run.

One practical warning about Nx specifically: its power comes from conventions, and conventions are what make it hard to leave. Generators write files in a particular shape, executors abstract over the underlying tools, and plugin defaults decide how projects are laid out. That is genuinely valuable at fifty engineers and genuinely oppressive at five. Adopt it because you need the governance, not because it appears more capable on a feature grid. Documentation: turborepo.com, nx.dev, and pnpm.io/workspaces.

Where this fits in a build pipeline

A monorepo tool orchestrates builds; it does not perform them. The bundler underneath is a separate decision covered in Vite compared with Turbopack and in the wider JavaScript build tools roundup.

For workspace commands and the package manager layer itself, see the npm, pnpm, and bun cheatsheet. To wire caching into CI so the remote cache is actually hit, the CI/CD pipeline guide and the GitHub Actions cheatsheet cover cache keys and job graphs.