@microsoft/rush, lerna, nx, and turbo are all tools designed to manage JavaScript/TypeScript monorepos—codebases that contain multiple packages or applications in a single repository. They address challenges like dependency hoisting, cross-package linking, task orchestration, and build optimization. While they share overlapping goals, their architectures, philosophies, and feature sets differ significantly. Rush emphasizes strict dependency management and enterprise-scale workflows; Lerna pioneered the space with simple package publishing and linking; Nx provides deep project graph analysis and computation caching with strong support for full-stack development; Turbo (from Vercel) focuses on high-speed incremental builds and remote caching with minimal configuration.
Managing a JavaScript monorepo isn’t just about putting multiple projects in one folder—it’s about solving hard problems: consistent dependencies, efficient builds, safe publishing, and developer velocity at scale. Let’s dissect how @microsoft/rush, lerna, nx, and turbo tackle these challenges.
@microsoft/rush uses its own install engine (wrapping PNPM by default) to enforce single-version policy: every package in the repo must use the same version of any given dependency. This eliminates subtle bugs from version skew but requires careful coordination during upgrades.
// rush.json
{
"pnpmVersion": "8.7.0",
"ensureConsistentVersions": true,
"projects": [
{ "packageName": "@myorg/ui", "projectFolder": "packages/ui" },
{ "packageName": "@myorg/api", "projectFolder": "apps/api" }
]
}
lerna delegates entirely to the underlying package manager (npm/yarn/pnpm). It doesn’t enforce version consistency—each package can have its own node_modules tree unless you enable useWorkspaces. This flexibility often leads to diamond dependency issues in practice.
nx is agnostic—it works atop npm, Yarn, or PNPM workspaces. However, it provides dependency constraints via nx.json to enforce rules like “libs cannot depend on apps”:
// nx.json
{
"targetDefaults": {
"build": { "dependsOn": ["^build"] }
},
"dependencyConstraints": {
"enforceBuildableLibDependency": true
}
}
turbo assumes you’re using npm/Yarn/PNPM workspaces and doesn’t interfere with dependency resolution. It focuses purely on task execution, not dependency hygiene.
rush defines tasks via command-line.json, supporting phased execution and custom parameters:
// common/config/rush/command-line.json
{
"commands": [
{
"commandKind": "bulk",
"name": "build",
"summary": "Build all projects",
"safeForSimultaneousRushProcesses": true
}
]
}
It runs tasks per-project but lacks deep awareness of what changed—you get parallelism, not intelligence.
lerna offers basic parallel/concurrent execution:
lerna run build --parallel
But it has no concept of task outputs or caching—every run rebuilds everything unless you script around it.
nx builds a project graph from source code imports and package.json dependencies. This enables nx affected:build to run only what’s needed:
# Only build projects affected by the last commit
nx affected:build --base=HEAD~1 --head=HEAD
Nx also supports computation caching—locally and via Nx Cloud—so identical inputs skip execution entirely.
turbo uses a pipeline definition to model task dependencies and cache outputs based on file hashes:
// turbo.json
{
"pipeline": {
"build": {
"outputs": ["dist/**"],
"dependsOn": ["^build"]
},
"test": {
"outputs": [],
"dependsOn": ["build"]
}
}
}
Run with turbo run build—it skips cached tasks and executes others in topological order.
rush has no built-in caching. You’d need to layer in something like Azure Artifacts or custom scripts.
lerna also lacks caching—every task runs from scratch.
nx caches based on source files, environment variables, and task configuration. The cache includes terminal output and artifacts. With Nx Cloud, teams share a remote cache:
# Enable remote caching
nx connect-to-nx-cloud
turbo caches task outputs (defined in turbo.json) using content hashes of inputs (source files, env vars, global hash). Remote caching is built-in via Vercel’s infrastructure:
# Login to enable remote cache
npx turbo login
Both Nx and Turbo invalidate correctly on code changes—but Nx’s project graph gives it finer granularity (e.g., skipping tests for unchanged libs).
rush excels here with change files—developers describe changes via rush change, then rush version auto-bumps versions and updates changelogs. Publishing is atomic and auditable:
rush change --type minor --message "Add dark mode"
rush version
rush publish --include-all
lerna popularized lerna version and lerna publish, but its fixed/independent modes are error-prone without additional tooling. No built-in change tracking.
nx doesn’t handle publishing natively—you’d use nx release (experimental) or integrate with Changesets or manually script it.
turbo has no publishing features. It’s purely a build orchestrator.
rush demands upfront investment: config files live in common/config/rush, and you must adopt its conventions. Great for enterprises, overkill for small teams.
lerna feels familiar but dated—its simplicity is now a liability as monorepo needs evolve.
nx offers generators (nx g @nx/react:lib ui) and lint rules that understand your project graph. Plugins exist for every major framework, and the VS Code extension shows dependency visualizations.
turbo wins on zero-config adoption—add turbo.json and you’re off. But you trade configurability for speed; no code generators, no dependency linting.
Despite differences, all four tools converge on key principles:
Each understands that a monorepo contains multiple logical projects and can run scripts across them:
# Common pattern: run test in all packages
rush test # Rush
lerna run test # Lerna
nx run-many -t test # Nx
turbo run test # Turbo
All support concurrent task execution to leverage multi-core machines:
# Example: build all projects in parallel
rush build --parallel
lerna run build --parallel
nx run-many -t build --parallel
They assume you’re using npm/Yarn/PNPM workspaces under the hood (except Rush, which wraps PNPM directly).
Even Lerna (via --since) tries to limit scope—though only Nx and Turbo do it robustly.
| Feature | @microsoft/rush | lerna | nx | turbo |
|---|---|---|---|---|
| Dependency Policy | Enforces single-version | No enforcement | Constraints via config | No enforcement |
| Task Intelligence | Basic parallelism | Basic parallelism | Project graph + affected | Pipeline + file hashing |
| Caching | None | None | Local + remote (Nx Cloud) | Local + remote (Vercel) |
| Publishing | Built-in (change files) | Built-in (basic) | Experimental / manual | None |
| Code Generation | No | No | Yes (via plugins) | No |
| Best For | Enterprise-scale, strict control | Legacy repos, simple publishing | Full-stack apps, DX-focused | Speed-focused, minimal config |
The Big Picture:
Choose based on whether your bottleneck is correctness (Rush), velocity (Nx), or raw speed (Turbo).
Choose Nx when you need intelligent task orchestration powered by a fine-grained project graph, extensive plugin ecosystem (React, Angular, NestJS, etc.), and local/remote computation caching. Nx shines in full-stack monorepos with interdependent apps and libs, offering features like affected commands, code generation, and distributed task execution. Its deep integration with modern frameworks and developer ergonomics make it a strong choice for teams prioritizing DX and long-term maintainability.
Opt for turbo when your top priority is build speed through incremental execution and remote caching with minimal setup. Turbo’s zero-config approach (especially with Turborepo) works well for teams already using npm or Yarn workspaces and wanting fast CI without complex pipeline definitions. It’s particularly effective in Vercel-hosted environments but lacks built-in code generation, dependency constraints, or deep framework integrations found in Nx.
Lerna is best suited for smaller or legacy monorepos where the primary needs are basic package linking and versioned publishing. While it once dominated the space, its core functionality has been largely superseded by more modern tools. Avoid using Lerna for new projects unless you specifically require its lightweight publishing model and don’t need advanced features like computation caching or deep task orchestration. For most greenfield efforts, consider Nx or Turbo instead.
Choose @microsoft/rush if you're operating at enterprise scale with strict requirements around dependency consistency, deterministic installs, and CI reproducibility. Rush enforces a single version policy for dependencies across projects and integrates tightly with PNPM via its own installer wrapper. It’s ideal for large teams needing auditability, change logs, and safe publishing workflows—but be prepared for higher configuration overhead and less flexibility in toolchain choices.
Get to green PRs in half the time. Nx optimizes your builds, scales your CI, and fixes failed PRs. Built for developers and AI agents.
Using npx
npx create-nx-workspace
Using npm init
npm init nx-workspace
Using yarn create
yarn create nx-workspace
Run:
npx nx@latest init