Monorepo Tools Comparison: Turborepo vs Nx vs Lerna in 2026
Compare Turborepo, Nx, and Lerna for JavaScript monorepos — build caching, task pipelines, code generation, and team fit. Includes configuration examples and mi
Monorepo Tools Comparison: Turborepo vs Nx vs Lerna in 2026
A monorepo keeps multiple projects — a web app, an API, shared component libraries, utility packages — in one repository. The appeal is obvious: one PR touches the API and the frontend together, shared code doesn't require publishing packages to npm, and CI can run only what's affected by a change.
The tooling choice matters because the wrong one either underdelivers on caching (slow CI) or overdelivers on complexity (engineers spend time learning the tool instead of building features).
When a Monorepo Makes Sense
Monorepos solve specific problems. Before adopting one, confirm you have them:
| Problem | Monorepo Helps | Monorepo Doesn't Help |
|---|---|---|
| Shared types between frontend and backend | ✅ | — |
| Coordinated releases across packages | ✅ | — |
| Consistent tooling (lint, test, format) | ✅ | — |
| Team of 2 with one app | — | ❌ (overhead not worth it) |
| Completely independent services, different stacks | — | ❌ (use polyrepo) |
| Slow CI for a single-package repo | — | ❌ (fix CI, not repo structure) |
Repository Structure (Common to All Tools)
All three tools work with a similar directory layout:
my-monorepo/
├── apps/
│ ├── web/ # Next.js frontend
│ ├── api/ # Fastify backend
│ └── mobile/ # React Native app
├── packages/
│ ├── ui/ # Shared React components
│ ├── types/ # Shared TypeScript types
│ ├── utils/ # Shared utilities
│ └── config/
│ ├── eslint/ # Shared ESLint config
│ ├── typescript/ # Shared tsconfig
│ └── jest/ # Shared Jest config
├── package.json # Root package.json (workspaces)
└── turbo.json / nx.json # Tool-specific config
Root package.json (pnpm workspaces):
{
"name": "my-monorepo",
"private": true,
"workspaces": ["apps/*", "packages/*"],
"scripts": {
"build": "turbo build",
"dev": "turbo dev --parallel",
"test": "turbo test",
"lint": "turbo lint"
},
"devDependencies": {
"turbo": "^2.0.0"
}
}
🌐 Looking for a Dev Team That Actually Delivers?
Most agencies sell you a project manager and assign juniors. Viprasol is different — senior engineers only, direct Slack access, and a 5.0★ Upwork record across 100+ projects.
- React, Next.js, Node.js, TypeScript — production-grade stack
- Fixed-price contracts — no surprise invoices
- Full source code ownership from day one
- 90-day post-launch support included
Turborepo
Turborepo (by Vercel) focuses on one thing: making build, test, and lint tasks fast through intelligent caching and parallelization. Its configuration is minimal, and it integrates with any build system.
turbo.json configuration:
{
"$schema": "https://turbo.build/schema.json",
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": [".next/**", "dist/**", "!dist/cache/**"],
"cache": true
},
"dev": {
"dependsOn": ["^build"],
"cache": false,
"persistent": true
},
"test": {
"dependsOn": ["build"],
"outputs": ["coverage/**"],
"cache": true
},
"lint": {
"cache": true
},
"type-check": {
"dependsOn": ["^type-check"],
"cache": true
}
}
}
Key concepts:
"^build"means "build all dependencies first" — Turbo resolves the dependency graph automaticallyoutputstells Turbo what to cache — on a cache hit, it restores the output files instead of re-runningpersistent: truefor dev tasks (never completes, so never cached)
Running with filtering:
# Build only affected packages (since last commit)
turbo build --filter=...[HEAD^1]
# Build a specific app and its dependencies
turbo build --filter=web...
# Run dev for the API only
turbo dev --filter=api
Remote caching with Vercel (free for open source, paid for teams):
# Link to Vercel remote cache
npx turbo login
npx turbo link
# Or self-host with Turborepo remote cache API
# Set TURBO_TEAM and TURBO_TOKEN env vars in CI
With remote cache, a CI job that's already been run on the same code takes seconds — Turbo downloads cached outputs instead of re-executing.
Turborepo strengths: Minimal config, fast by default, excellent Vercel integration, zero lock-in (still uses your existing build tools).
Turborepo limitations: No code generation, no project templates, no understanding of your code's structure — it just runs tasks. For teams that want generators and structural enforcement, Nx is more capable.
Nx
Nx is a full-featured monorepo platform. Beyond caching, it understands your project graph (who imports whom), can generate code, enforces module boundaries, and has plugins for every major framework.
nx.json configuration:
{
"$schema": "./node_modules/nx/schemas/nx-schema.json",
"defaultBase": "main",
"namedInputs": {
"default": ["{projectRoot}/**/*", "sharedGlobals"],
"production": [
"default",
"!{projectRoot}/**/*.spec.ts",
"!{projectRoot}/jest.config.ts"
],
"sharedGlobals": []
},
"targetDefaults": {
"build": {
"dependsOn": ["^build"],
"inputs": ["production", "^production"],
"cache": true
},
"test": {
"inputs": ["default", "^production"],
"cache": true
},
"lint": {
"inputs": ["default"],
"cache": true
}
},
"plugins": [
"@nx/next/plugin",
"@nx/eslint/plugin"
]
}
Nx project configuration (apps/web/project.json):
{
"name": "web",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "apps/web",
"projectType": "application",
"tags": ["type:app", "scope:web"],
"targets": {
"build": {
"executor": "@nx/next:build",
"options": {
"outputPath": "dist/apps/web"
}
},
"dev": {
"executor": "@nx/next:dev",
"options": { "port": 3000 }
}
}
}
Module boundary enforcement (prevents packages from importing things they shouldn't):
// .eslintrc.json
{
"rules": {
"@nx/enforce-module-boundaries": [
"error",
{
"enforceBuildableLibDependency": true,
"allow": [],
"depConstraints": [
{
"sourceTag": "scope:web",
"onlyDependOnLibsWithTags": ["scope:web", "scope:shared"]
},
{
"sourceTag": "scope:api",
"onlyDependOnLibsWithTags": ["scope:api", "scope:shared"]
},
{
"sourceTag": "type:app",
"onlyDependOnLibsWithTags": ["type:lib", "type:util"]
}
]
}
]
}
}
Code generation (Nx generators):
# Generate a new Next.js app
nx g @nx/next:app dashboard
# Generate a new shared React library
nx g @nx/react:library ui-components --publishable
# Generate a component inside the ui library
nx g @nx/react:component Button --project=ui-components --export
Nx strengths: Full project graph analysis, code generation, module boundary enforcement, excellent for large teams with complex dependency rules.
Nx limitations: Steeper learning curve, more configuration, the plugin system can feel heavy for simple monorepos.
🚀 Senior Engineers. No Junior Handoffs. Ever.
You get the senior developer, not a project manager who relays your requirements to someone you never meet. Every Viprasol project has a senior lead from kickoff to launch.
- MVPs in 4–8 weeks, full platforms in 3–5 months
- Lighthouse 90+ performance scores standard
- Works across US, UK, AU timezones
- Free 30-min architecture review, no commitment
Lerna
Lerna was the original JavaScript monorepo tool, primarily focused on package versioning and publishing. It's been revitalized by Nx Inc. (Lerna 6+) and now uses Nx for task running under the hood.
lerna.json:
{
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
"version": "independent",
"npmClient": "pnpm",
"command": {
"publish": {
"conventionalCommits": true,
"message": "chore(release): publish"
}
}
}
Publishing packages:
# Bump versions and publish all changed packages
lerna publish --conventional-commits
# Publish specific package
lerna publish --scope=@myorg/ui
Lerna's publishing workflow — detecting which packages changed, bumping versions according to conventional commits, updating changelogs, and publishing to npm — is its primary differentiator. Turborepo and Nx don't do this natively.
Lerna strengths: Best npm publish workflow, conventional commit versioning, changelog generation.
Lerna limitations: Primarily for publishable packages — if you're not publishing to npm, Turbo or Nx are better choices.
Decision Guide
Are you publishing packages to npm?
├── YES → Lerna (+ Nx for task running)
└── NO → Do you have 20+ developers with complex dependency rules?
├── YES → Nx (module boundaries, generators, project graph)
└── NO → Turborepo (minimal config, fast, get started in 30 min)
Team size heuristics:
- 1–10 engineers: Turborepo — minimal overhead, fast to learn
- 10–50 engineers: Either Turborepo or Nx depending on complexity
- 50+ engineers: Nx — the generator and boundary enforcement pays off at scale
Shared Package Setup (Works with All Tools)
// packages/types/src/index.ts
export interface User {
id: string;
email: string;
role: 'admin' | 'editor' | 'viewer';
}
export interface ApiResponse<T> {
data: T;
error?: string;
meta?: Record<string, unknown>;
}
// packages/types/package.json
{
"name": "@myorg/types",
"version": "0.0.1",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"scripts": {
"build": "tsc",
"type-check": "tsc --noEmit"
}
}
// apps/web/package.json — consuming the shared package
{
"dependencies": {
"@myorg/types": "workspace:*"
}
}
With workspace protocol (workspace:*), pnpm links to the local package instead of fetching from npm. Changes to @myorg/types are immediately visible to apps/web — no publish step needed.
CI Configuration (Turborepo Example)
# .github/workflows/ci.yml
name: CI
on: [push, pull_request]
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # Full history for Turbo's affected calculation
- uses: pnpm/action-setup@v3
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- run: pnpm install --frozen-lockfile
- name: Build and test affected
run: pnpm turbo build test lint --filter=...[origin/main]
env:
TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
TURBO_TEAM: ${{ secrets.TURBO_TEAM }}
With --filter=...[origin/main], Turbo only runs tasks for packages changed since the last merge to main. On a 20-package monorepo where you changed 2 packages, this runs 2 builds instead of 20.
Working With Viprasol
We set up and maintain monorepo infrastructure for product teams moving from spaghetti multi-repo setups to structured workspaces. The typical outcome: CI times drop 60–80%, shared code is actually shared instead of copy-pasted, and cross-team PRs become routine rather than painful.
→ Talk to our team about your repository architecture.
See Also
- CI/CD Pipeline Setup — pipelines that run efficiently in monorepos
- DevOps Best Practices — GitHub Actions and build optimization
- TypeScript Advanced Patterns — shared type packages
- Software Architecture Patterns — monorepo fits modular monolith pattern
- Web Development Services — frontend architecture and tooling
About the Author
Viprasol Tech Team
Custom Software Development Specialists
The Viprasol Tech team specialises in algorithmic trading software, AI agent systems, and SaaS development. With 100+ projects delivered across MT4/MT5 EAs, fintech platforms, and production AI systems, the team brings deep technical experience to every engagement. Based in India, serving clients globally.
Need a Modern Web Application?
From landing pages to complex SaaS platforms — we build it all with Next.js and React.
Free consultation • No commitment • Response within 24 hours
Need a custom web application built?
We build React and Next.js web applications with Lighthouse ≥90 scores, mobile-first design, and full source code ownership. Senior engineers only — from architecture through deployment.