{"slug": "monorepos-in-2026-turborepo-vs-nx-vs-bazel-what-actually-works", "title": "Monorepos in 2026: Turborepo vs Nx vs Bazel — What Actually Works", "summary": "The article summarizes the state of monorepo tools in 2026, concluding that Turborepo has become the default for most JavaScript/TypeScript teams due to its simplicity and sensible defaults, while Nx serves enterprises needing complex dependency graph management and Bazel remains the choice for Google-scale projects. It notes that monorepos became standard because they eliminate the productivity loss of managing multiple repositories, and provides practical insights from running all three tools in production. The author emphasizes that for most teams, Bazel is overkill, and the key tradeoffs are between Turborepo's ease of use and Nx's advanced features like affected commands and distributed caching.", "body_md": "# Monorepos in 2026: What Actually Works\n\nThe monorepo debate in 2026 has settled into something more mature. Turborepo became the default for most JS/TS teams, Nx found its niche in enterprises with complex dependency graphs, and Bazel still dominates at Google scale. Here's what I've learned from running all three in production.\n\n## Why Monorepos Won\n\nLet's be clear about why monorepos became the default in 2026:\n\n-\n**Atomic commits across services**— Change an API contract and update all consumers in one PR -\n**Shared tooling**— One ESLint config, one Prettier config, one TypeScript config -\n**Easy refactoring**— Rename a function used across 20 packages without coordination -\n**Unified CI/CD**— One pipeline that understands dependency relationships -\n**Developer experience**— One`git clone`\n\n, one`npm install`\n\n, everything works\n\nThe alternative — polyrepo hell where a simple rename requires 15 PRs across 15 repos — killed productivity. Teams migrated.\n\n## Turborepo: The Default Choice\n\nTurborepo won because it has sensible defaults, a gentle learning curve, and just works for most JavaScript/TypeScript projects. It's not the most powerful, but it's the most practical.\n\n### Setting Up a Turborepo Project in 2026\n\n```\n// package.json (root)\n{\n  \"name\": \"my-monorepo\",\n  \"private\": true,\n  \"workspaces\": [\"apps/*\", \"packages/*\"],\n  \"scripts\": {\n    \"build\": \"turbo build\",\n    \"dev\": \"turbo dev\",\n    \"lint\": \"turbo lint\",\n    \"test\": \"turbo test\"\n  },\n  \"devDependencies\": {\n    \"turbo\": \"^2.0.0\",\n    \"typescript\": \"^5.4.0\"\n  }\n}\n// turbo.json\n{\n  \"$schema\": \"https://turbo.build/schema.json\",\n  \"tasks\": {\n    \"build\": {\n      \"dependsOn\": [\"^build\"],\n      \"outputs\": [\".next/**\", \"!.next/cache/**\", \"dist/**\"]\n    },\n    \"dev\": {\n      \"cache\": false,\n      \"persistent\": true\n    },\n    \"lint\": {\n      \"dependsOn\": [\"^build\"]\n    },\n    \"test\": {\n      \"dependsOn\": [\"build\"],\n      \"outputs\": [\"coverage/**\"],\n      \"inputs\": [\"src/**/*.tsx\", \"src/**/*.ts\", \"test/**/*.ts\"]\n    }\n  }\n}\n```\n\n### Real Package Dependencies\n\n```\napps/\n  web/          → depends on @repo/ui, @repo/api-client\n  admin/        → depends on @repo/ui, @repo/api-client\n  docs/         → depends on @repo/ui, @repo/docs-utils\npackages/\n  ui/           → no internal deps\n  api-client/   → no internal deps\n  docs-utils/   → no internal deps\n  config/       → no internal deps (ESLint, TypeScript configs)\n```\n\n### The Cache That Actually Works\n\n``` bash\n# First build (slow)\n$ turbo build\n• building web\n• building admin\n• building docs\n• building ui\n• building api-client\n• building docs-utils\n\n→ Tasks run by turbo: 6 (64ms each, 384ms total)\n\n# Second build (instant - cache hit)\n$ turbo build\n• building web (cache hit)\n• building admin (cache hit)\n• building docs (cache hit)\n• building ui (cache hit)\n• building api-client (cache hit)\n• building docs-utils (cache hit)\n\n→ Tasks run by turbo: 0 (6 in cache, 12ms total)\n```\n\n## Nx: When You Need the Power\n\nNx is Turborepo on steroids. It understands your project graph at a deeper level, has built-in code generators, affected commands, and powerful visualization tools. The tradeoff: more complexity, steeper learning curve.\n\n### Nx Project Setup\n\n```\n# Create Nx workspace\nnpx create-nx-workspace@latest myorg --preset=ts\n\n# Add a new application\nnpx nx generate @nx/nextjs:app admin\n\n# Add a library\nnpx nx generate @nx/js:library api-client --directory=packages/api-client\n```\n\n### The Affected Command (Nx's Killer Feature)\n\n```\n# Only build/test/lint what changed (and what depends on what changed)\nnpx nx affected --target=build --base=origin/main\n\n# This is the game-changer for large monorepos\n# Instead of rebuilding everything, Nx computes the dependency graph\n# and only rebuilds the affected packages\n```\n\n### Visualizing the Project Graph\n\n```\n# Opens browser with interactive dependency graph\nnpx nx graph\n```\n\nThis generates a visual graph of your entire project. You can see exactly which packages depend on which, spot circular dependencies, and understand the impact of proposed changes.\n\n### Nx Computation Cache (Distributed)\n\n```\n// nx.json\n{\n  \"namedInputs\": {\n    \"default\": [\"{projectRoot}/**/*\"],\n    \"production\": [\"!{projectRoot}/**/*.spec.ts\"]\n  },\n  \"targetDefaults\": {\n    \"build\": {\n      \"dependsOn\": [\"^build\"],\n      \"cache\": true,\n      \"inputs\": [\"production\", \"^production\"]\n    }\n  },\n  \"nxCloudAccessToken\": \"your-nx-cloud-token\"\n}\n```\n\nNx Cloud provides distributed caching — your CI server and local machine share the same build cache. Cold CI builds become warm builds instantly.\n\n## Bazel: At Google Scale\n\nBazel is the nuclear option. It's infinitely scalable, hermetically sealed (reproducible builds), and can handle truly massive codebases. The tradeoff: significant operational overhead, Starlark learning curve, and ecosystem fragmentation outside the Google ecosystem.\n\n### When Bazel Makes Sense\n\n- 1,000+ engineers in a single codebase\n- Multiple languages (TypeScript + Python + Go + Rust + Java)\n- Need per-language optimized tooling\n- hermetic builds are a hard requirement\n- You're okay with dedicated build infrastructure team\n\nFor most teams, Bazel is overkill. The rule of thumb: if you're asking whether you need Bazel, you don't.\n\n## The Migration Path\n\n### From Polyrepo to Turborepo\n\n**Step 1: Create the monorepo structure**\n\n```\nmkdir my-monorepo && cd my-monorepo\ngit init\nnpm init -y\nnpm install -D turbo\nmkdir apps packages\n```\n\n**Step 2: Move packages one at a time**\n\n```\n# Move one package\nmv ../old-repo/packages/ui apps/ui\ncd apps/ui\nnpm init -y\n# Update package.json name to @myorg/ui\ncd ../..\ngit add -A\ngit commit -m \"feat: move ui package\"\n```\n\n**Step 3: Fix dependency issues**\n\n```\n// Before (old package.json)\n{\n  \"name\": \"ui\",\n  \"dependencies\": {\n    \"react\": \"^18.0.0\"\n  }\n}\n\n// After (in monorepo)\n{\n  \"name\": \"@myorg/ui\",\n  \"peerDependencies\": {\n    \"react\": \"^18.0.0\"\n  }\n}\n```\n\n**Step 4: Add shared configs**\n\n```\n# packages/config-eslint/index.js\nmodule.exports = {\n  extends: ['next/core-web-vitals', 'turbo'],\n  rules: {\n    '@typescript-eslint/no-unused-vars': 'error'\n  }\n};\n\n# packages/config-typescript/base.json\n{\n  \"compilerOptions\": {\n    \"target\": \"ES2022\",\n    \"lib\": [\"ES2022\"],\n    \"strict\": true,\n    \"esModuleInterop\": true,\n    \"skipLibCheck\": true\n  }\n}\n```\n\n## The Hidden Costs Nobody Warns You About\n\n### Build Tool Lockstep\n\nWhen you share a `config`\n\npackage across 20 apps, updating TypeScript becomes a coordinated event. You can't have App A on TypeScript 5.3 and App B on 5.4 while they're in the same repo.\n\n**Solution**: Use `pnpm workspaces`\n\nwith version pinning:\n\n```\n# pnpm-workspace.yaml\npackages:\n  - 'apps/*'\n  - 'packages/*'\n\n# .npmrc\nsave-exact=true\n```\n\n### The Big Git History Question\n\nDo you keep git history when migrating?\n\n```\n# Option 1: Keep history (preserve all commit messages)\ngit subtree add --prefix=apps/web https://github.com/old/web.git main\n\n# Option 2: Fresh start (cleaner, lose history)\nmv old-repo/web apps/web\n```\n\nI recommend Option 2 for most migrations. Git history for individual packages in a monorepo is rarely useful, and the migration complexity isn't worth it.\n\n### CI/CD Complexity\n\n```\n# GitHub Actions with Turborepo\nname: CI\n\non: [push, pull_request]\n\njobs:\n  affected:\n    name: Check affected\n    runs-on: ubuntu-latest\n    outputs:\n      affected: ${{ steps.turbo.outputs.affected }}\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n      - uses: pnpm/action-setup@v3\n      - uses: actions/setup-node@v4\n        with:\n          node-version: 20\n          cache: pnpm\n      - run: pnpm install --frozen-lockfile\n      - id: turbo\n        run: echo \"affected=$(pnpm turbo run build --dry-run=json | jq -r '.packages | if length > 0 then \"true\" else \"false\" end')\" >> $GITHUB_OUTPUT\n\n  build:\n    needs: affected\n    if: needs.affected.outputs.affected == 'true'\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: pnpm/action-setup@v3\n      - uses: actions/setup-node@v4\n        with:\n          node-version: 20\n          cache: pnpm\n      - run: pnpm install --frozen-lockfile\n      - run: pnpm turbo build --filter=...affected packages...\n```\n\n## 2026 Tooling Ecosystem\n\n| Tool | Language Agnostic | Learning Curve | Scale | Best For |\n|---|---|---|---|---|\n| Turborepo | JS/TS mostly | Gentle | < 100 packages | Most teams |\n| Nx | Multiple | Medium | < 500 packages | Enterprises |\n| Bazel | Any | Steep | Unlimited | Google-scale |\n| Lerna | JS/TS | Gentle | < 50 packages | Legacy (deprecated) |\n\n## My Recommendation\n\n**For most teams in 2026**: Start with Turborepo. It's the right default. You get 80% of the benefit for 20% of the complexity.\n\n**If you're in an enterprise** with complex dependencies, code generators, and a dedicated platform team: Nx is worth the investment.\n\n**If you're at Google scale**: Bazel. But you already knew that.\n\nThe worst choice: using no monorepo tooling at all. Running a monorepo with just `npm workspaces`\n\nand manual dependency tracking is technical debt that compounds.\n\n*Running a monorepo? What's your tool of choice in 2026? Let's hear real-world experiences.*\n\n*Further reading: Turborepo docs, Nx docs, Bazel migration guide*", "url": "https://wpnews.pro/news/monorepos-in-2026-turborepo-vs-nx-vs-bazel-what-actually-works", "canonical_source": "https://dev.to/zny10289/monorepos-in-2026-turborepo-vs-nx-vs-bazel-what-actually-works-1j85", "published_at": "2026-05-23 20:44:55+00:00", "updated_at": "2026-05-23 21:02:10.397768+00:00", "lang": "en", "topics": ["developer-tools", "open-source", "enterprise-software"], "entities": ["Turborepo", "Nx", "Bazel", "Google"], "alternates": {"html": "https://wpnews.pro/news/monorepos-in-2026-turborepo-vs-nx-vs-bazel-what-actually-works", "markdown": "https://wpnews.pro/news/monorepos-in-2026-turborepo-vs-nx-vs-bazel-what-actually-works.md", "text": "https://wpnews.pro/news/monorepos-in-2026-turborepo-vs-nx-vs-bazel-what-actually-works.txt", "jsonld": "https://wpnews.pro/news/monorepos-in-2026-turborepo-vs-nx-vs-bazel-what-actually-works.jsonld"}}