Monorepo Tools: Turborepo, Nx, Bazel, Lage Comparison
Introduction
Monorepos — storing multiple projects in a single repository — offer simplified dependency management, atomic commits, and shared tooling. But without the right build tool, monorepos become slow as they grow. This article compares four leading monorepo orchestration tools: Turborepo, Nx, Bazel, and Lage.
Turborepo
Vercel's build orchestrator focuses on caching and task scheduling:
// turbo.json
{
"$schema": "https://turbo.build/schema.json",
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/**"],
"cache": {
"inputs": ["src/**", "tsconfig.json"],
"outputMode": "new-only"
}
},
"test": {
"dependsOn": ["build"],
"outputs": [],
"inputs": ["src/**", "*.test.ts"]
},
"lint": {
"outputs": []
},
"dev": {
"cache": false,
"persistent": true
}
}
}
# Run builds across all workspaces in dependency order
turbo run build
# Run with parallel execution (default: CPU count)
turbo run build --parallel
# Dry run to see execution plan
turbo run build --dry
# Filter specific packages
turbo run build --filter=packages/core...
# Remote caching (Vercel)
turbo run build --remote-only
**Strengths**: Fastest setup, excellent documentation, incremental adoption, parallel execution, remote caching with Vercel integration. The caching granularity (file-level inputs) is well-designed.
**Weaknesses**: Limited to JavaScript/TypeScript ecosystem, less sophisticated dependency graph analysis than Nx, remote caching requires Vercel.
Nx
Nx provides build orchestration with powerful code generation and dependency analysis:
# Create an Nx workspace
npx create-nx-workspace@latest myorg
# Add capabilities
nx g @nx/next:app my-app
nx g @nx/react:lib shared-ui
nx g @nx/node:lib api-interfaces
# Run tasks
nx run-many -t build test lint
# Visualize dependencies
nx graph
# Affected commands (only run what changed)
nx affected:test --base=main
// nx.json
{
"tasksRunnerOptions": {
"default": {
"runner": "nx-cloud", // or "nx/tasks-runners/default"
"options": {
"cacheableOperations": ["build", "test", "lint", "e2e"],
"parallel": 5,
"accessToken": "your-token"
}
}
},
"targetDefaults": {
"build": {
"dependsOn": ["^build"],
"inputs": ["!{projectRoot}/test/**/*"]
}
}
}
**Strengths**: Language-agnostic, powerful dependency graph analysis, code generation reduces boilerplate, affected commands save CI time, plugin ecosystem extends to any tool.
**Weaknesses**: Steeper learning curve, configuration overhead for simple projects, opinionated directory structure, Nx Cloud costs for advanced features.
Bazel
Google's build system prioritizes correctness and hermetic builds:
# BUILD.bazel
load("@npm//:defs.bzl", "npm_link_all_packages")
npm_link_all_packages(name = "node_modules")
load("@aspect_rules_ts//ts:defs.bzl", "ts_project")
ts_project(
name = "core",
srcs = glob(["src/**/*.ts"]),
deps = [
"//packages/utils",
"@npm//lodash",
"@npm//zod",
],
tsconfig = "//:tsconfig",
out_dir = "dist",
)
# python_binary for a data science component
load("@rules_python//python:defs.bzl", "py_binary")
py_binary(
name = "data_pipeline",
srcs = ["pipeline.py"],
deps = ["@pypi//pandas"],
)
# Build with caching and parallelism
bazel build //packages/core:all
# Run tests
bazel test //packages/...
# Query dependency graph
bazel query "deps(//packages/core)"
# Remote execution
bazel build --config=remote //...
**Strengths**: Correct by design (hermetic builds), polyglot (JS, Python, Go, Java in one repo), remote build execution, fine-grained caching, handles largest monorepos.
**Weaknesses**: Very steep learning curve, verbose configuration, not JavaScript-native, slower cold builds, requires significant infrastructure.
Lage
Microsoft's task runner focused on simplicity:
// lage.config.js
module.exports = {
pipeline: {
build: ["^build"],
test: ["build"],
lint: [],
typecheck: [],
},
npmClient: "pnpm",
cache: true,
concurrency: 8,
};
# Run the build pipeline
lage build
# Run tests
lage test
# Show execution plan
lage build --dry
# Filter by scope
lage build --scope packages/core
**Strengths**: Simple configuration, fast, good Microsoft-ecosystem integration, incremental builds, lightweight.
**Weaknesses**: Smaller community, fewer features than Turborepo/Nx, limited plugin ecosystem, primarily JS/TS.
Comparison
| Feature | Turborepo | Nx | Bazel | Lage |
|---------|-----------|-----|-------|------|
| Setup time | Minutes | Minutes | Hours | Minutes |
| Caching | File-level | File-level | Content-addressed | File-level |
| Remote caching | Vercel only | Nx Cloud | Built-in | No |
| Polyglot | No | Yes | Yes | No |
| Code generation | No | Yes | No | No |
| Learning curve | Low | Medium | High | Low |
| Ideal repo size | Small-large | Small-large | Large | Small-medium |
Recommendations
* **Start simple**: Use Turborepo for most JavaScript/TypeScript monorepos — best setup experience.
* **Need code generation**: Nx reduces boilerplate significantly for large teams.
* **Polyglot monorepo**: Nx or Bazel if you need multiple languages in one repo.
* **Maximum scale and correctness**: Bazel for massive monorepos with strict build requirements.
* **Simple needs within Microsoft ecosystem**: Lage for lightweight orchestration.