I've shipped two monorepo codebases in the past year that went into production. One runs on Nx, the other on Turborepo. They weren't experiments. Both are actively maintained client projects, one a healthcare platform with shared Angular libraries, the other a greenfield order management system on TanStack Start and Fastify.
After living with both, here's what I'd tell you if you asked me over coffee.
What Turborepo is actually good at
Turborepo's pitch is simple: fast task pipelines through caching and parallelism. If you have a pnpm workspace and you want build and test to run in the right order without repeating work, Turborepo delivers that with almost no configuration overhead.
The mental model is low-friction. You define a turbo.json with task dependencies, and it figures out what can run in parallel and what's cached. The remote cache story (Vercel Remote Cache) is genuinely great once you're set up, dropping CI from 8 minutes to under 90 seconds on a warm cache.
Where it doesn't go: Turborepo is a build orchestrator, not an architecture tool. It won't generate code, enforce module boundaries, or tell you that your ui library is importing directly from api. That's fine. It knows what it is. But if you need those things, you're on your own.
The order entry platform at Billiet runs on Turborepo with React 19, TanStack Start, and Fastify. It works well. The project is structured enough that we don't need boundary enforcement, and the caching is the main reason we chose it.
What Nx is actually good at
Nx is more opinionated, and that's by design. The Nx team believes that a monorepo tool should understand what your code does, not just what commands to run.
The result: Nx has generators (for creating new libraries, components, API endpoints), enforced module boundaries (via @nx/enforce-module-boundaries ESLint rule), affected-analysis that understands import graphs, and first-class Angular support that's kept pace with the framework.
The NURS-E healthcare platform at Wit-Gele Kruis runs on Nx. We have web, mobile, and shared library packages. The module boundaries rule is the feature I reach for most, preventing at least three "oops, that import was the wrong direction" bugs that would have been nasty to untangle later. The generators kept our Angular component structure consistent across a team of four.
The cost: Nx is more to learn and more to configure. The project.json per-package setup feels heavier than turbo.json. And the Angular plugin's integration can be finicky when you're on a cutting-edge Angular version.
The honest comparison
| Turborepo | Nx | |
|---|---|---|
| Setup time | Low | Medium |
| Caching | Excellent | Good |
| Code generation | None built-in | Generators + plugins |
| Module boundaries | No | Yes (ESLint rule) |
| Angular support | Generic | First-class |
| Remote cache | Vercel Remote Cache | Nx Cloud |
| Learning curve | Gentle | Steeper |
How to choose
Choose Turborepo if your team already knows their architecture and just needs faster CI. It's also a better fit for polyglot repos where you don't want any one tool imposing structure; Turborepo just runs your scripts efficiently.
Choose Nx if you're building a platform that will grow, especially with Angular. The boundary enforcement and generators pay dividends as the team scales. The upfront investment in configuration is real, but so is the long-term payoff.
The wrong choice is treating this as a permanent commitment. Both tools support incremental adoption. I've moved a project from raw pnpm workspaces to Turborepo in half a day. Migrating between the two is harder, but not impossible.
What I'd do next time
For a greenfield project with a small team and a tight deadline, I'd pick Turborepo and accept the tradeoff of manual discipline for boundaries. For a platform with more than three people and a long runway, I'd start with Nx and invest the time upfront.
The bigger mistake I see is people spending weeks evaluating the tools instead of shipping. Both are good. Pick one, move.