Turborepo vs Nx in 2026: Monorepo Tooling After Two Years
TL;DR
Turborepo wins for simplicity; Nx wins for large enterprise monorepos with complex constraints. If you're a startup or mid-size team that wants fast, cached builds with minimal configuration overhead — Turborepo is the right call. It's simpler to understand, integrates naturally into any package.json workflow, and the caching "just works." Nx has a steeper learning curve but pays off at scale: project graph visualization, enforced module boundaries, code generation, and team of 30+ workflows all favor Nx. For most teams: start with Turborepo. If you find yourself needing Nx's features, the migration is straightforward.
Key Takeaways
- Turborepo: simpler setup, better for "I just need caching and task orchestration"
- Nx: more opinionated, powerful project graph, enforced boundaries, better code generation
- Remote caching: both offer cloud caching; Turborepo via Vercel (paid), Nx Cloud (free tier)
- Bundle: Turborepo ~2MB Rust binary; Nx is Node.js-based
- Download trends: both growing; Nx has more downloads (~4M/week vs ~2M for Turborepo)
What Each Tool Does
Turborepo (Vercel, 2021):
→ Task runner with intelligent caching
→ Works on top of existing npm/pnpm/yarn workspaces
→ turbo.json defines tasks and their dependencies
→ Local + remote caching (cache outputs, skip unchanged work)
→ Pipeline visualization
→ Relatively minimal — doesn't prescribe project structure
Nx (Nrwl, 2019 as "nx", earlier as angular-cli extensions):
→ Full monorepo framework
→ Project graph: understands all imports/dependencies between packages
→ Generators: create apps, libraries, components with `nx g`
→ Executors: custom build/test/serve targets per project type
→ Module boundary enforcement: "library X cannot import from Y"
→ Affected commands: `nx affected:test` only tests what changed
→ NX Cloud: remote caching + distributed task execution
→ Opinionated workspace structure
Key conceptual difference:
Turborepo: "cache the outputs of your existing scripts"
Nx: "understand your entire workspace and optimize based on that knowledge"
Setup and Configuration
// ─── Turborepo setup ───
// 1. Add turbo to existing monorepo:
// package.json (root):
{
"devDependencies": {
"turbo": "^2.0.0"
},
"workspaces": ["apps/*", "packages/*"]
}
// 2. turbo.json — define the task pipeline:
{
"$schema": "https://turbo.build/schema.json",
"tasks": {
"build": {
"dependsOn": ["^build"], // build deps first
"inputs": ["$TURBO_DEFAULT$", ".env*"],
"outputs": ["dist/**", ".next/**"]
},
"test": {
"dependsOn": ["build"],
"inputs": ["src/**", "tests/**"]
},
"lint": {
"inputs": ["src/**"]
},
"dev": {
"cache": false,
"persistent": true
}
}
}
// 3. Run tasks:
// turbo build -- builds all packages, respecting dependencies, caching outputs
// turbo build --filter=@myapp/web -- build only web app and its deps
// turbo test --filter=[HEAD^1] -- test only changed since last commit
// ─── Nx setup ───
// nx.json (root config):
{
"$schema": "./node_modules/nx/schemas/nx-schema.json",
"targetDefaults": {
"build": {
"dependsOn": ["^build"],
"inputs": ["default", "^default"],
"cache": true
},
"test": {
"inputs": ["default", "^production", "{workspaceRoot}/jest.preset.js"],
"cache": true
}
},
"namedInputs": {
"default": ["{projectRoot}/**/*", "sharedGlobals"],
"production": ["default", "!{projectRoot}/src/tests/**/*"]
}
}
// project.json (per-project):
{
"name": "web-app",
"tags": ["scope:web", "type:app"],
"targets": {
"build": {
"executor": "@nx/next:build",
"options": { "outputPath": "dist/apps/web-app" }
}
}
}
// Run tasks:
// nx build web-app -- build with cache
// nx affected --target=test -- test only affected by current changes
// nx graph -- visual dependency graph in browser
Caching: The Core Value
# Both tools hash inputs and cache outputs
# Turborepo cache hit:
turbo build
# cache hit, replaying logs packages/ui:build
# cache hit, replaying logs packages/utils:build
# cache miss apps/web:build ← only this runs
# Total time: 2.3s (would have been 45s without cache)
# Remote caching — Turborepo (Vercel Remote Cache):
# package.json:
{
"scripts": {
"build": "turbo build --token=$TURBO_TOKEN --team=myteam"
}
}
# Free tier: limited
# Paid tier: unlimited cache storage + team sharing
# Remote caching — Nx Cloud:
# nx.json:
{
"nxCloudId": "your-workspace-id"
}
# Free tier: generous (enough for most teams)
# Paid: higher limits + distributed task execution (DTX)
# Distributed task execution (Nx Enterprise feature):
# nx run-many --target=test --all -- runs tests across multiple machines
# Nx orchestrates which machine runs which test
# Turborepo doesn't have equivalent (local parallelism only)
# Speed comparison (500-file monorepo, warm cache):
# Turborepo: ~1.5s (Rust binary, fast startup)
# Nx: ~3s (Node.js, slightly slower startup)
# Cold cache: similar — depends on actual work being cached
Module Boundary Enforcement (Nx Only)
// Nx's "module boundaries" enforce architectural rules:
// .eslintrc.json:
{
"plugins": ["@nx/eslint-plugin"],
"rules": {
"@nx/enforce-module-boundaries": ["error", {
"allow": [],
"depConstraints": [
{
// Apps can only depend on libraries, not other apps
"sourceTag": "type:app",
"onlyDependOnLibsWithTags": ["type:lib", "type:util"]
},
{
// Data access libraries cannot depend on UI libraries
"sourceTag": "type:data-access",
"notDependOnLibsWithTags": ["type:ui"]
},
{
// Shared utilities can only depend on other utilities
"sourceTag": "scope:shared",
"onlyDependOnLibsWithTags": ["scope:shared"]
}
]
}]
}
}
// Tag your projects in project.json:
// apps/checkout: tags: ["scope:checkout", "type:app"]
// libs/ui: tags: ["scope:shared", "type:ui"]
// libs/auth-data: tags: ["scope:auth", "type:data-access"]
// Now if checkout app tries to import from billing app:
import { BillingService } from '@myapp/billing'; // ← ESLint error!
// Error: "A project tagged with 'type:app' can only depend on libs"
// This prevents:
// → "Big ball of mud" architectures where everything imports everything
// → Circular dependencies between domains
// → Business logic leaking into UI layers
// Turborepo doesn't have this — it's just a task runner
// For enforcement in Turborepo: add eslint-plugin-import/no-cycle + manual rules
Project Graph (Nx) and When It Matters
# Nx builds a complete dependency graph:
nx graph
# Opens browser at localhost:4211 with interactive graph:
# → All projects as nodes
# → All dependencies as edges
# → Highlight affected projects from a change
# → Filter to show subsets
# Affected commands — only run what's impacted:
nx affected --target=build --base=main --head=HEAD
# Analyzes git diff → maps files to projects → builds dependency graph
# → Builds only projects affected by the changes
# → Skips unchanged packages that would pass anyway
# Example: you changed libs/auth/src/jwt.ts
# Nx knows:
# → libs/auth is directly changed
# → apps/web depends on libs/auth (must rebuild)
# → apps/mobile depends on libs/auth (must rebuild)
# → apps/marketing does NOT depend on libs/auth (skip)
# → libs/payments depends on libs/auth (must rebuild)
# Turborepo equivalent:
turbo build --filter=[HEAD^1] # or --filter=...[main]
# Similar concept, but less granular — works at file hash level
# Nx's graph analysis is more precise for affected detection
When to Choose Each
Choose Turborepo when:
→ You want faster builds with minimal new concepts to learn
→ Your team already understands npm workspaces — just add caching
→ You're a startup moving fast (turbo.json is ~20 lines to start)
→ You don't need module boundary enforcement or code generation
→ You prefer a minimal tool that does one thing well
Choose Nx when:
→ You have 5+ teams working in the same monorepo
→ You need enforced architectural boundaries between domains
→ You want code generation (nx generate @nx/react:component)
→ Distributed task execution across CI machines matters
→ You're building multiple app types (React, Node, mobile) in one repo
→ The visual dependency graph would help your team
The migration path:
→ Start with Turborepo
→ If you find yourself manually enforcing module boundaries: add Nx
→ Nx can run alongside Turborepo; many teams use Nx Cloud for caching with Turborepo
Real recommendation (2026):
→ 1-3 devs: neither — npm workspaces with a Makefile is enough
→ 3-15 devs: Turborepo — fast, simple, effective
→ 15-50 devs: Turborepo with Nx Cloud caching OR Nx
→ 50+ devs: Nx — the architectural governance features justify the complexity
Compare Turborepo and Nx download trends at PkgPulse.
See the live comparison
View turborepo vs. nx on PkgPulse →