DevToolBoxFREE
Blog

Monorepo Tools 2026: Turborepo vs Nx vs Lerna vs pnpm Workspaces Compared

15 min readby DevToolBox

A monorepo (single repository for multiple packages and apps) improves code sharing, refactoring, and dependency management. In 2026, the three dominant approaches are Turborepo (fast caching, simple config), Nx (powerful generators and affected detection), and pnpm Workspaces (lightweight, no additional tooling). This guide covers setup, task caching, and CI integration for all three.

Turborepo: Setup and Structure

Turborepo is optimized for JavaScript/TypeScript monorepos. It uses a simple pipeline definition and aggressive caching to skip tasks whose inputs haven't changed.

# Create a new Turborepo monorepo
npx create-turbo@latest my-monorepo
cd my-monorepo

# Or add Turborepo to an existing monorepo
npm install turbo --save-dev

# Directory structure
my-monorepo/
├── apps/
│   ├── web/          # Next.js app
│   └── docs/         # Docs site
├── packages/
│   ├── ui/           # Shared React components
│   ├── utils/        # Shared utilities
│   └── tsconfig/     # Shared TypeScript configs
├── turbo.json        # Pipeline configuration
└── package.json      # Root workspace config

Turborepo Pipeline Configuration

The turbo.json pipeline defines how tasks relate to each other and what outputs to cache. The ^ prefix means "wait for dependent packages to build first."

// turbo.json — pipeline definition
{
    "$schema": "https://turbo.build/schema.json",
    "pipeline": {
        "build": {
            "dependsOn": ["^build"],   // ^ means: run in dependency order
            "outputs": [".next/**", "!.next/cache/**", "dist/**"]
        },
        "test": {
            "dependsOn": ["^build"],
            "inputs": ["src/**/*.tsx", "src/**/*.ts", "test/**/*.ts"]
        },
        "lint": {
            "outputs": []
        },
        "dev": {
            "cache": false,            // Never cache dev servers
            "persistent": true         // Long-running task
        },
        "type-check": {
            "dependsOn": ["^build"],
            "outputs": []
        }
    },
    "globalEnv": ["NODE_ENV", "DATABASE_URL"]
}

# Run all build tasks (uses cache if inputs unchanged)
npx turbo build

# Run only for specific apps/packages
npx turbo build --filter=web
npx turbo build --filter=...ui  # ui and all its dependents

# Force re-run (bypass cache)
npx turbo build --force

# View task graph
npx turbo build --graph

Nx: Setup and Project Graph

Nx provides a richer feature set: code generators, affected commands (only run tasks for changed code), and first-class support for many frameworks including React, Angular, Node, and .NET.

# Create Nx monorepo
npx create-nx-workspace@latest my-nx-repo --preset=ts

# Add Nx to existing monorepo
npx nx@latest init

# Generate a new app or library
nx generate @nx/next:app web
nx generate @nx/react:library ui
nx generate @nx/node:library utils

# nx.json — workspace configuration
{
    "tasksRunnerOptions": {
        "default": {
            "runner": "nx/tasks-runners/default",
            "options": {
                "cacheableOperations": ["build", "test", "lint", "e2e"],
                "parallel": 3
            }
        }
    },
    "targetDefaults": {
        "build": {
            "dependsOn": ["^build"],
            "inputs": ["production", "^production"]
        }
    },
    "namedInputs": {
        "default": ["{projectRoot}/**/*", "sharedGlobals"],
        "production": ["default", "!{projectRoot}/**/?(*.)+(spec|test).[jt]s?(x)"],
        "sharedGlobals": []
    }
}

pnpm Workspaces: Lightweight Approach

pnpm workspaces provide native monorepo support without additional tooling. Combine with Turborepo or Nx for caching, or use alone for simpler projects.

# pnpm-workspace.yaml
packages:
  - 'apps/*'
  - 'packages/*'

# Install all workspace dependencies
pnpm install

# Add a package to a specific workspace
pnpm add react --filter @myapp/web

# Add a local workspace package as dependency
# In apps/web/package.json:
{
    "dependencies": {
        "@myapp/ui": "workspace:*"
    }
}

# Run scripts across all packages
pnpm --filter '*' run build
pnpm --filter './apps/*' run dev
pnpm --filter '...@myapp/ui' run test  # ui and dependents

# Publish all public packages
pnpm publish -r --access public

Remote Caching

Remote caching shares build artifacts between developers and CI systems. A cache hit means instant builds — no recompilation needed if inputs are unchanged.

# Turborepo Remote Cache (Vercel)
# Enables sharing build cache across developers and CI

# 1. Link to Vercel
npx turbo login
npx turbo link

# 2. Now CI builds share cache with local dev:
# CI run: builds from scratch, uploads to cache
# Developer: pulls cache, gets instant builds

# Self-host with Turborepo Remote Cache (open source)
# docker run -p 3000:3000 ducktors/turborepo-remote-cache

# .turbo/config.json (auto-generated by turbo link)
{
    "teamId": "team_xxx",
    "apiUrl": "https://vercel.com"
}

# Nx Cloud (similar offering from Nx)
# nx connect-to-nx-cloud
# Provides remote caching + task distribution

Versioning with Changesets

Changesets is the standard tool for versioning and publishing packages in a monorepo. It tracks which packages changed and generates CHANGELOG.md entries.

# Changesets — versioning and publishing for monorepos
# Works with npm/yarn/pnpm workspaces

pnpm add -D @changesets/cli
pnpm changeset init

# When you make a change that needs a version bump:
pnpm changeset
# → Interactive prompt: which packages changed, bump type (major/minor/patch)

# Creates a markdown file in .changeset/ describing the change
# Example .changeset/funny-bears-dance.md:
---
"@myapp/ui": minor
"@myapp/utils": patch
---

Add new Button component with loading state

# Apply changesets (bumps versions, updates CHANGELOG.md)
pnpm changeset version

# Publish all changed packages
pnpm changeset publish

Turborepo vs Nx vs pnpm Workspaces

FeatureTurborepoNxpnpm Workspaces
Setup complexityLowMediumVery Low
Task cachingBuilt-inBuilt-inManual/external
Remote cacheVercel (free tier)Nx Cloud (paid)None built-in
Code generatorsNoYes (rich)No
Affected detectionBasic (--filter)Advanced (nx affected)Via Changesets
Language supportJS/TS focusedPolyglotAny
Learning curveLowMedium-HighLow

Best Practices

  • Start with pnpm workspaces + Turborepo — lowest friction, excellent caching, good documentation.
  • Define clear boundaries: shared packages in packages/, applications in apps/. Never import app code from a shared package.
  • Use Changesets for versioning and publishing. Automate releases with GitHub Actions on merge to main.
  • Enable remote caching early — Turborepo's Vercel cache is free for hobby projects. CI time drops from minutes to seconds.
  • Keep each package's package.json explicit: list all direct dependencies even if they are hoisted. This prevents phantom dependency bugs.

Frequently Asked Questions

Should I use Turborepo or Nx?

Turborepo is simpler and faster to set up — ideal for most JavaScript/TypeScript projects. Nx has more features: code generators, affected detection, module boundaries enforcement, and better support for polyglot repos (Java, .NET). Choose Nx if you need those features or are working on a large enterprise repo.

What is the difference between monorepo and polyrepo?

A monorepo stores all code in one repository. A polyrepo (multi-repo) has separate repositories per package or service. Monorepos make cross-package refactoring easier and ensure all packages are always compatible. Polyrepos have simpler CI and more team autonomy. Most companies at scale use monorepos.

How does Turborepo caching work?

Turborepo hashes the inputs to each task (source files, environment variables, dependencies). If the hash matches a previous run, it restores outputs from cache (locally or remotely). The cache is content-addressable, so identical inputs always produce cache hits regardless of when they were created.

Can I have different package managers in a monorepo?

No. All workspaces in a monorepo must use the same package manager. The workspace protocol (workspace:*) is implemented differently in npm, yarn, and pnpm. Pick one and stick with it. pnpm is the most efficient for monorepos due to its symlink-based node_modules.

How do I handle environment variables in a monorepo?

Define environment variables per app (each app has its own .env). For shared secrets, use a secrets manager (Vault, AWS SSM). In Turborepo, list environment variables in turbo.json globalEnv (for all tasks) or per-task env array. Turborepo includes env vars in the cache key to prevent stale cache hits.

Related Tools

𝕏 Twitterin LinkedIn
Was this helpful?

Stay Updated

Get weekly dev tips and new tool announcements.

No spam. Unsubscribe anytime.

Try These Related Tools

{ }JSON Formatter.git.gitignore Generator

Related Articles

Bun Package Manager: The Fastest JavaScript Runtime and Package Manager in 2026

Complete guide to Bun in 2026: installation, workspace management, scripts, and why it's faster than npm/yarn/pnpm. Benchmarks, migration guide, and real-world usage.

Docker Multi-Stage Builds: Optimizing Images for Production

Master Docker multi-stage builds in 2026. Reduce image sizes dramatically, separate build and runtime dependencies, and create optimized production Docker images.

Git Branching Strategies: GitFlow vs Trunk-Based vs GitHub Flow

Compare GitFlow, Trunk-Based Development, and GitHub Flow branching strategies. Learn branch structures, merge workflows, CI/CD integration, and how to choose the right strategy for your team.