Skip to main content

Turborepo vs Nx vs Moon: Best Monorepo Tools in 2026

·PkgPulse Team
0

TL;DR

Turborepo is the default for JavaScript monorepos in 2026 — simple config, Vercel's remote caching, and zero opiniated framework coupling. Nx wins for large enterprise teams needing code generation, affected graph analysis, and deep framework integration (Angular, React, Next.js). Moon is the rising alternative with Rust-based performance, task inheritance, and the first true multi-language monorepo support (JS + Rust + Go). For most teams: start with Turborepo. If you hit its limits, evaluate Nx or Moon.

Key Takeaways

  • Turborepo: 700K downloads/week, Vercel-owned, simple turbo.json, remote cache built-in, Rust-rewritten in v2
  • Nx: 2.5M downloads/week, enterprise features, code generators, affected commands, plugin ecosystem
  • Moon: 50K downloads/week, Rust-based (fastest), multi-language, task inheritance, project graph
  • Remote caching: Turborepo → Vercel Remote Cache (free for Vercel users); Nx → Nx Cloud; Moon → Moonbase
  • Learning curve: Turborepo (low) < Moon (medium) < Nx (high)

Downloads

PackageWeekly DownloadsTrend
nx~2.5M→ Stable (enterprise standard)
turbo~700K↑ Growing
@moonrepo/cli~50K↑ Fast growing

Turborepo: Simple and Fast

# Create new Turborepo:
npx create-turbo@latest my-monorepo
cd my-monorepo && pnpm install
// turbo.json — minimal config:
{
  "$schema": "https://turbo.build/schema.json",
  "tasks": {
    "build": {
      "dependsOn": ["^build"],
      "outputs": [".next/**", "dist/**"]
    },
    "test": {
      "dependsOn": ["^build"],
      "cache": true
    },
    "dev": {
      "persistent": true,
      "cache": false
    },
    "lint": {
      "dependsOn": ["^lint"]
    },
    "type-check": {
      "dependsOn": ["^build"]
    }
  }
}
// package.json workspace config:
{
  "name": "my-monorepo",
  "workspaces": ["apps/*", "packages/*"],
  "scripts": {
    "build": "turbo build",
    "dev": "turbo dev",
    "test": "turbo test",
    "lint": "turbo lint"
  }
}
# Running tasks:
pnpm turbo build                    # Build all packages in dependency order
pnpm turbo build --filter=web       # Build only "web" app and its deps
pnpm turbo build --filter=...web    # Build web and all packages that depend on it
pnpm turbo build --filter=web...    # Build web and all its dependencies
pnpm turbo build --force            # Skip cache

# Remote caching (free with Vercel):
npx turbo login
npx turbo link  # Link to Vercel project for remote cache

Turborepo v2 Changes (Rust Rewrite)

# v2 performance improvement over v1 (Node.js):
# Cold start:   Node.js 480ms → Rust 45ms (-90%)
# Cached run:   Node.js 220ms → Rust 18ms (-92%)
# Hot path:     Near-zero overhead

# v2 new features:
# - Granular task environment variable hashing
# - Better --filter syntax
# - Watch mode for persistent tasks

Nx: Enterprise-Grade Monorepo

npx create-nx-workspace@latest my-workspace
# Choose: apps (Next.js, React, etc.) or integrated or package-based
// nx.json:
{
  "$schema": "./node_modules/nx/schemas/nx-schema.json",
  "targetDefaults": {
    "build": {
      "dependsOn": ["^build"],
      "cache": true
    },
    "test": {
      "cache": true
    }
  },
  "namedInputs": {
    "default": ["{projectRoot}/**/*", "sharedGlobals"],
    "production": ["default", "!{projectRoot}/**/?(*.)+(spec|test).*"]
  },
  "defaultBase": "main"
}
# Nx generators — create new projects from templates:
nx generate @nx/next:app my-next-app
nx generate @nx/react:lib ui-components
nx generate @nx/node:lib shared-utils

# Affected commands — only run what changed:
nx affected --target=build             # Build only affected projects
nx affected --target=test              # Test only affected projects
nx affected --base=main --head=HEAD    # Compare to main branch

# Project graph visualization:
nx graph  # Opens browser with interactive dependency graph

# Nx Console — VS Code extension:
# GUI for running tasks and generating code
// project.json — per-project task config:
{
  "name": "my-app",
  "$schema": "../../node_modules/nx/schemas/project-schema.json",
  "projectType": "application",
  "targets": {
    "build": {
      "executor": "@nx/next:build",
      "outputs": ["{options.outputPath}"],
      "options": {
        "outputPath": "dist/apps/my-app"
      }
    },
    "test": {
      "executor": "@nx/jest:jest",
      "options": {
        "jestConfig": "apps/my-app/jest.config.ts"
      }
    },
    "deploy": {
      "executor": "nx:run-commands",
      "dependsOn": ["build"],
      "options": {
        "command": "vercel --prod dist/apps/my-app"
      }
    }
  }
}

Moon: Rust-Powered Multi-Language

# Install Moon:
npm install --save-dev @moonrepo/cli
# Or globally:
npm install -g moon
moon init
# .moon/workspace.yml:
node:
  version: '22.0.0'
  packageManager: 'pnpm'

projects:
  globs:
    - 'apps/*'
    - 'packages/*'

vcs:
  manager: 'git'
  defaultBranch: 'main'
# moon.yml (root-level task inheritance):
tasks:
  build:
    command: 'pnpm build'
    inputs:
      - 'src/**/*'
      - 'tsconfig.json'
    outputs:
      - 'dist'
    deps:
      - '^:build'

  test:
    command: 'pnpm test'
    deps:
      - '^:build'

  lint:
    command: 'pnpm lint'
    inputs:
      - 'src/**/*'
      - '*.config.*'
# apps/web/moon.yml — project-level overrides:
type: 'application'
language: 'typescript'

tasks:
  # Inherit and extend root tasks:
  build:
    deps:
      - 'packages/ui:build'  # Explicit dependency
  
  # App-specific tasks:
  deploy:
    command: 'vercel --prod'
    deps:
      - '~:build'  # ~ = this project's build
    env:
      VERCEL_ORG_ID: '$VERCEL_ORG_ID'
# Moon commands:
moon run web:build          # Run build for web app
moon run :build             # Run build for ALL projects
moon run web:build --affected  # Only run if web changed
moon check                  # Validate all project configs
moon project-graph          # Show dependency graph (like nx graph)
moon query projects         # List all projects

Why Monorepo Tools Became Essential in 2026

JavaScript monorepos started as a workaround — Facebook (Yarn workspaces) and Google (Bazel) invented the pattern for massive codebases. In 2020-2022, the tooling matured enough for typical companies to adopt them. By 2026, most teams with more than 3-4 related packages are using monorepos, and the build tool choice defines the developer experience.

The core problem that Turborepo, Nx, and Moon solve: task orchestration at scale. In a monorepo with 20 packages, pnpm -r build builds all packages sequentially or in naive parallel — it doesn't understand that building @myapp/web depends on @myapp/ui which depends on @myapp/utils. More importantly, it can't cache results: if @myapp/utils didn't change, why rebuild it? Monorepo tools build a dependency graph and execute only what's necessary, with caching.

The difference between the tools is philosophy:

Turborepo's philosophy: simple configuration, get out of the way. A turbo.json file declares task dependencies and cache outputs. That's 90% of what you need. Turborepo doesn't scaffold your code, doesn't care about your framework, and doesn't require a specific project structure. If you have an existing npm workspace with well-structured packages, adding Turborepo takes 30 minutes.

Nx's philosophy: a development platform, not just a build tool. Nx wants to be involved in every aspect of your workflow — code generation, task running, migration scripts, affected analysis, and CI optimization. The trade-off is high configuration investment upfront that pays off at scale. Teams with 50+ packages and multiple frameworks in one repo find Nx's structure worth it. Teams with 5 packages find it heavy.

Moon's philosophy: correctness and multi-language support. Moon was written in Rust for performance and correctness from the start. Its task inheritance model (define tasks once at the root, override per project) avoids the copy-paste package.json scripts problem that Turborepo and Nx both have. Moon's unique differentiator is true polyglot support: you can define Rust, Go, Python, and Node.js tasks in the same workspace with the same tooling. No other monorepo tool handles this.


Caching Comparison

TurborepoNxMoon
Local cache
Remote cacheVercel Remote CacheNx CloudMoonbase
Remote cache (free)✅ (Vercel users)500 credits/monthLimited
Remote cache (self-host)✅ (via HTTP)✅ (Nx Cloud Enterprise)
Cache invalidationFile hashes + env varsFile hashes + env varsFile hashes + env vars + OS
Cache hit speed~18ms (Rust)~200ms (Node.js)~12ms (Rust)
# Self-hosted remote cache for Turborepo (free alternative to Vercel):
# Use ducktape, turborepo-remote-cache, or Vercel's open protocol

# GitHub Actions remote cache (free):
- name: Setup Turborepo Remote Cache
  uses: dtinth/setup-github-actions-caching-for-turbo@v1

Feature Comparison

FeatureTurborepoNxMoon
Learning curveLowHighMedium
Code generators✅ Extensive
Affected analysis--filteraffected--affected
Project graphBasic✅ Visual graph✅ Visual graph
Multi-language❌ JS onlyPartial✅ JS/Rust/Go
Task inheritanceLimitedVia presets✅ Hierarchical
Plugin ecosystemSmallLargeGrowing
VS Code extensionBasic✅ Nx ConsolePartial
Enterprise supportVercelNrwl/Nxmoonrepo

Adopting a Monorepo Tool: A Migration Path

Most teams don't start with a monorepo tool — they start with a monorepo that gets slow. A typical evolution:

Stage 1: npm/pnpm workspaces, no tool. You have apps/web and packages/ui. pnpm -r build works. CI takes 10 minutes because it rebuilds everything. This works until you have 5+ packages or a team of 5+ people.

Stage 2: Add Turborepo. Install turbo, add turbo.json with task definitions, and point your npm scripts at turbo build/test/lint. Remote cache is optional initially — even the local cache cuts CI time substantially because unchanged packages don't rebuild. For most teams, this is the natural upgrade path. The Turborepo team designed the setup to be a two-hour project.

Stage 3: Evaluate Nx if you hit Turborepo's limits. The typical Turborepo limits teams hit: (a) they need code generation for consistent scaffolding across 20+ packages, (b) they have Angular in the monorepo and want first-class integration, (c) affected analysis isn't granular enough at scale. Nx's migration from Turborepo is documented and tooled — npx nx init can bootstrap an Nx workspace from an existing Turbo setup.

Stage 4: Consider Moon if you're adding non-JS languages. Moon is the right choice when your monorepo genuinely spans languages. A backend API in Node.js, a CLI in Rust, and infrastructure code in Python can all be tasks in Moon's workspace. No other tool handles this cleanly without customization.

The common mistake is over-engineering upfront: picking Nx for a 3-package monorepo "for scale" creates unnecessary ceremony. Start simple, upgrade when the pain arrives.

Real-World Benchmarks

The performance difference between these tools is meaningful but shouldn't drive the decision alone. On a cold run (no cache), the task scheduling overhead is:

  • Moon: ~12ms per task overhead (Rust binary)
  • Turborepo v2: ~18ms per task overhead (Rust rewrite from v1)
  • Nx: ~200ms+ per task overhead (Node.js, JIT startup)

On a hot run (full cache hit), all three complete in <50ms total — the difference is negligible. Where performance matters in practice is not the 200ms overhead but the cache hit rate: how accurately does each tool determine which tasks need to re-run?

Turborepo and Nx both use file hash + environment variable hashing. Moon goes further and tracks operating system information and tool versions in the cache key, which reduces false positives (cache hits on actually-changed environments) at the cost of more frequent cache misses.

For most teams, the cache hit rate difference between the three tools is under 5%, and the developer experience difference (configuration complexity, debugging tooling, error messages) matters far more.

Decision Guide

Choose Turborepo if:
  → New monorepo, want simplest setup
  → Already on Vercel (free remote caching)
  → Small-to-medium team (< 20 engineers)
  → JavaScript/TypeScript only
  → Want minimal config overhead

Choose Nx if:
  → Large enterprise team (50+ engineers)
  → Need code generation for scaffolding
  → Angular or NestJS in the monorepo
  → Want the nx affected workflow in CI
  → Already invested in Nx plugins

Choose Moon if:
  → Multi-language monorepo (JS + Rust/Go)
  → Want task inheritance (hierarchical config)
  → Maximum build cache performance
  → Exploring alternatives to Vercel/Nrwl lock-in
  → Greenfield with future multi-language needs

Common Monorepo Pitfalls and How These Tools Help

Before looking at CI numbers, it's worth naming the problems teams run into with unmanaged monorepos that prompted the tooling category to exist.

Phantom dependencies. In a monorepo with npm workspaces, packages can accidentally import from sibling packages without declaring them as dependencies — the modules are hoisted to the root node_modules and available everywhere. This breaks when you deploy a single package: it imports something that's not in its own package.json. Strict pnpm workspace config (shamefully-hoist: false) fixes this at the package manager level; Nx and Moon also catch this at the task graph level.

Task ordering bugs. Running pnpm -r build in parallel mode can succeed locally but fail in CI because two packages build in the wrong order — Package B builds before Package A finishes, and B imports A's types. Turborepo, Nx, and Moon all solve this by building a task dependency graph before execution: dependsOn: ["^build"] (Turborepo/Nx) or deps: ["^:build"] (Moon) ensures parent packages build before children.

Stale cache issues. Sometimes a cache is too aggressive. If your build outputs aren't fully enumerated in outputs, Turborepo might serve a cached output that doesn't include new files. The fix is explicit output paths. Moon's hashing includes environment variables by default, which helps with cache correctness but sometimes causes more cache misses than desired.

Version inconsistency across packages. When multiple packages in a monorepo use different versions of React or TypeScript, errors become cryptic. Tools like Syncpack (works with all three) check version consistency. Nx can enforce version constraints via project tags. This isn't a monorepo tool feature per se, but it's a common workflow concern these tools address through ecosystem integrations.

CI/CD Integration: What the Numbers Show

The real payoff of monorepo tools is in CI — specifically in reducing the build time for pull requests by running only affected packages.

Without a monorepo tool: A PR touching @myapp/utils rebuilds and retests every package in CI. For a 20-package monorepo, this means running all 20 builds and all 20 test suites. If your test suite takes 5 minutes per package, that's 100 minutes per PR.

With Turborepo's --filter=...HEAD and remote caching: Only the directly affected packages and their downstream dependents run. Plus, packages unchanged since the last CI run hit the remote cache and finish in milliseconds. Real-world teams report 70-90% CI time reductions after adding remote caching.

Nx's affected commands (nx affected --target=build) take this further by using git diff to compute the exact affected project graph. Nx also provides first-class GitHub Actions integration via nx-set-shas — a simple action that sets the correct base commit SHA for accurate affected detection across branches.

For teams where CI cost is a real budget concern, the choice of monorepo tool is directly tied to the bottom line. Nx's affected analysis is the most sophisticated; Turborepo's remote cache setup is the fastest to configure; Moon's Moonbase is the newest but already production-ready.

One often-overlooked CI factor is GitHub Actions cache storage. The default cache storage limit is 10GB per repository — large monorepos with many packages and cache artifacts can hit this limit, causing cache evictions and unexpected CI slowdowns. Turborepo's Vercel Remote Cache and Nx Cloud use their own storage and don't count against GitHub's limit. Moon's Moonbase similarly offloads cache storage. This is a practical consideration for repositories accumulating artifacts over months of active development.

Why Remote Caching Changes the Economics of CI

The headline feature of all three tools is build caching — but remote caching is what makes monorepos viable at scale, and the economics differ significantly between the tools.

Without remote caching, every CI run rebuilds everything. In a 30-package monorepo with Next.js apps and shared libraries, a CI run can take 15-25 minutes. With remote caching, a PR that only touches one package runs in under 2 minutes: the cache hit rate for unchanged packages is typically 90-95%.

Turborepo + Vercel Remote Cache is the cheapest path for teams already on Vercel. If you deploy to Vercel, remote caching is free and automatically linked to your Vercel organization. Cache artifacts are stored in Vercel's infrastructure and shared across all team members and CI runners. For teams NOT on Vercel, Turborepo supports any HTTP-compatible remote cache — community projects like turborepo-remote-cache provide a self-hostable option that works with S3, GCS, or Azure Blob Storage.

Nx Cloud charges after 500 credits/month on the free tier. For large teams with many CI runs, this adds up. Nx Cloud does offer a self-hosted option (Nx Cloud Enterprise), but the pricing is opaque and scales with team size. The self-hosted option requires running Nx Cloud infrastructure yourself.

Moon + Moonbase has the simplest pricing model but is the least mature. Moonbase is Moon's managed remote cache, with a more generous free tier. For teams evaluating Moon for multi-language monorepos, the caching infrastructure is sufficient but lacks the polished analytics of Nx Cloud.

The practical recommendation: if cost matters, Turborepo with self-hosted remote cache (using the open HTTP protocol) is the most flexible. Nx Cloud's detailed analytics are worth the cost for large engineering organizations where understanding which packages cause the most CI slowdowns is actionable.

CI Integration Patterns

How each tool integrates with GitHub Actions matters as much as local performance. Here's the standard pattern for each:

# Turborepo + GitHub Actions:
name: CI
on: [push, pull_request]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 2  # Needed for turbo --filter

      - uses: pnpm/action-setup@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: 'pnpm'

      - run: pnpm install --frozen-lockfile

      # Turbo remote cache via Vercel
      - name: Build + Test
        run: turbo build test lint type-check
        env:
          TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }}
          TURBO_TEAM: ${{ vars.TURBO_TEAM }}
# Nx + GitHub Actions:
- name: Nx affected
  run: npx nx affected --target=build,test,lint --base=origin/main --head=HEAD
  env:
    NX_CLOUD_AUTH_TOKEN: ${{ secrets.NX_CLOUD_AUTH_TOKEN }}
# Moon + GitHub Actions:
- name: Moon affected
  run: moon run :build :test --affected --base=origin/main

The key difference: Turborepo's --filter syntax requires knowing which packages changed; Nx's affected command infers this from the dependency graph automatically. Moon's --affected flag works similarly to Nx. For large monorepos with complex dependency graphs, Nx's affected analysis is more reliable — it understands task dependencies at a deeper level.

Migrating to a Monorepo Tool

The hardest part of adopting Turborepo, Nx, or Moon is not the tool itself — it's reorganizing a collection of separate projects into a coherent monorepo structure. Here's the minimal migration path for Turborepo:

  1. Consolidate package.json workspaces: All apps and packages need to live under apps/ or packages/ and be listed in the root package.json workspaces field.

  2. Create turbo.json: Start with the minimal config — just build and test tasks with dependsOn: ["^build"] for any package that needs its dependencies built first.

  3. Replace per-package scripts with turbo run build test at the root. Delete the individual npm run commands from CI.

  4. Add caching: The first time you enable remote cache and see a CI run go from 15 minutes to 45 seconds is genuinely striking.

Teams often underestimate step 1. Projects with different Node.js versions, different test runners, or inconsistent TypeScript configs require normalization before monorepo tooling becomes beneficial. Nx's migration guide (nx init) can add Nx to an existing workspace without reorganizing anything — this is a legitimate advantage for teams that want incremental adoption.

Compare monorepo tool downloads on PkgPulse.

Compare Nx and Turborepo package health on PkgPulse.

The monorepo tooling space continues to mature quickly. Turborepo added TypeScript project references support and better watch mode in 2025; Nx added more generators for popular frameworks; Moon shipped project constraints and improved its task profiling UI. The choice today is better than it was two years ago — all three tools are production-ready for teams of different sizes and needs.

See also: Best Monorepo Tools in 2026: Turborepo vs Nx vs Moon and Turborepo vs Nx vs Moon: Monorepo Tools Compared 2026, Turborepo vs Nx vs Moon 2026: Caching & CI Speed.

The 2026 JavaScript Stack Cheatsheet

One PDF: the best package for every category (ORMs, bundlers, auth, testing, state management). Used by 500+ devs. Free, updated monthly.