- Published on
Build Systems & Monorepo Tools 2026 — Bazel 8 · Pants 2 · Buck2 · Nx 20 · Turborepo 2 · Moon · Lerna 8 · Rush · pnpm Workspaces Deep Dive
- Authors

- Name
- Youngju Kim
- @fjvbn20031
Prologue — why build systems got interesting again
A 2026 platform meeting at a typical company.
Junior: "We're a JS monorepo, Turborepo is enough, right?" Senior: "Then how do we build the Go backend and the Python data team together?" Junior: "...oh."
That tiny exchange has all of 2026 in it. On one side: language-native tools (cargo, gradle, npm scripts). On the other side: polyglot systems (Bazel, Buck2, Pants). And between them, the task runner + cache layer (Nx, Turborepo, Moon).
This piece is the full map as of May 2026. Bazel 8 with Bzlmod by default, Buck2 settling as OSS, Pants 2 owning Python, the Nx 20 vs Turborepo 2 split, Moon climbing the polyglot ladder, and Gradle 8.10 / Maven 4 / sbt 1.10 in JVM land. Plus what Korean and Japanese big tech actually ships with.
1. The 2026 build-system map — two axes
There's too much packed into the phrase "build system." The easiest cut is two axes.
| Axis 1 / Axis 2 | Single language | Polyglot |
|---|---|---|
| Local cache only | cargo, npm, go build | GNU Make, just, Task |
| Remote cache/exec | Gradle + Develocity, Nx Cloud, Turborepo Remote Cache | Bazel, Buck2, Pants |
Vertical: language coverage. Horizontal: cache/distribution depth. The 2026 trend is that the bottom-right cell (polyglot + remote) is filling up fast. And within that cell, two camps split: the Bazel/Buck2/Pants camp ("declarative graph + interpreted BUILD files") and the Nx/Turborepo/Moon camp ("JSON/YAML config + run only affected projects").
Starting point is simple.
- Single language + small team → that language's native tool (cargo, gradle, npm).
- Single language + large monorepo → native tool + remote cache (Develocity, Nx Cloud).
- JS/TS-heavy + multiple packages → pnpm workspaces / Nx / Turborepo.
- Polyglot + large scale → Bazel / Buck2 / Pants.
2. Monorepo vs polyrepo — the 2026 answer
This debate is basically settled. The answer is "both, but monorepo is the default."
Monorepo wins: atomic changes (one PR touches library, service, tests at once), single dependency graph, shared infra (build, CI, lint), one-IDE search and refactor.
Polyrepo wins: permission isolation, build-time isolation, team autonomy, the simplicity of a small repo.
The 2026 compromise: "several monorepos inside one company." For example platform-monorepo (infra), product-monorepo (services), data-monorepo (pipelines). Each one is a monorepo, but company-wide it's polyrepo. Parts of Google, Meta, and Microsoft run this pattern. For a small company, just have one monorepo.
Trunk-based development + feature flags + change detection (see section 21) together erase most of the historical monorepo downsides.
3. Bazel 8 — the polyglot build standard
Bazel is the OSS version of Google's internal Blaze. Stable in May 2026 is Bazel 8.x, LTS is 7.x. The major shift: Bzlmod (MODULE.bazel) is now the default and WORKSPACE mode is deprecated.
License: Apache 2.0. Used by Google, X (formerly Twitter), Stripe, Pinterest, Spotify, Snap, Dropbox, Coupang, Mercari.
Core concepts.
BUILD.bazel: one per directory, declares build targets in that directory.WORKSPACE/MODULE.bazel: repo root, declares external deps and toolchains.- Starlark: Python subset, the language for writing build rules.
- rules: language-specific rule sets (
rules_go,rules_python,rules_js,rules_rust,rules_java). - sandbox: every action runs in an isolated directory, guaranteeing hermeticity.
- remote cache / remote execution: actions are keyed by hash, misses are dispatched to remote workers.
Simplest Go binary.
# BUILD.bazel
load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
go_library(
name = "hello_lib",
srcs = ["main.go"],
importpath = "example.com/hello",
)
go_binary(
name = "hello",
embed = [":hello_lib"],
)
bazel build //cmd/hello:hello
bazel test //...
bazel query 'deps(//cmd/hello:hello)' | head
Bzlmod dependency declaration.
# MODULE.bazel
module(name = "myrepo", version = "0.1.0")
bazel_dep(name = "rules_go", version = "0.55.0")
bazel_dep(name = "gazelle", version = "0.41.0")
go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps")
go_deps.from_file(go_mod = "//:go.mod")
use_repo(go_deps, "com_github_pkg_errors")
Bazel's strength is that graph + cache + remote execution are deeply integrated. The weakness is the learning curve and the chore of hand-writing BUILD files (mitigated by generators like Gazelle).
4. Buck2 (Meta) — Buck rewritten in Rust
Buck1 was Meta's Bazel cousin from 2013. In 2023 Meta rewrote it from scratch in Rust and released it OSS — Buck2. As of 2026 it's the standard internal build at Meta, and OSS adoption has been growing.
Compared with Bazel.
| Item | Bazel | Buck2 |
|---|---|---|
| Implementation | Java | Rust |
| Config language | Starlark | Starlark |
| Action graph | Determined at rule evaluation | Dynamic Dependencies, can mutate mid-build |
| Remote execution | REAPI (BuildBuddy, EngFlow) | REAPI (BuildBuddy, EngFlow) |
| License | Apache 2.0 | Apache 2.0 |
| Main users | Google, X, Stripe | Meta, Discord |
The biggest difference is dynamic dependencies. Bazel separates BUILD evaluation → action graph → execution, so the graph can't change once the build starts. Buck2 lets some nodes declare additional inputs mid-build, which naturally captures things like OCaml .ml / .mli dependencies driven by compilation output.
# BUCK
load("@prelude//rules.bzl", "cxx_binary", "cxx_library")
cxx_library(
name = "hello_lib",
srcs = ["hello.cc"],
headers = ["hello.h"],
)
cxx_binary(
name = "hello",
srcs = ["main.cc"],
deps = [":hello_lib"],
)
buck2 build //cpp/hello:hello
buck2 test //...
buck2 cquery 'deps(//cpp/hello:hello)'
Is migrating from Bazel worth it? Usually no. Only consider Buck2 for a new polyglot monorepo + Meta-influenced org + genuine need for dynamic deps.
5. Pants 2 — the de-facto Python monorepo
Pants v1, from Twitter in 2014, was a Java/Scala build system. Starting with v2, Toolchain Labs rebuilt it from scratch as a Python-first polyglot. Stable in May 2026 is Pants 2.27.
License: Apache 2.0. Used at Slack, IBM, Pico, Toolchain.
What sets Pants 2 apart.
- Python-first: first-class support for
pytest,mypy,ruff,pyright,black,flake8. - Automatic dependency inference: import graph analysis adds deps to BUILD files automatically.
- Lockfile-aware: integrates PEP 621 / pyproject.toml with lockfiles.
- Remote caching: compatible with BuildBuddy and EngFlow.
# pants.toml
[GLOBAL]
pants_version = "2.27.0"
backend_packages = [
"pants.backend.python",
"pants.backend.python.lint.ruff",
"pants.backend.python.typecheck.mypy",
"pants.backend.docker",
]
[python]
interpreter_constraints = ["==3.12.*"]
# src/python/myapp/BUILD
python_sources(name="lib")
python_tests(name="tests", dependencies=[":lib"])
pex_binary(name="bin", entry_point="myapp.main:main")
pants tailor :: # auto-generate BUILD files
pants lint test check :: # lint/test/typecheck everything
pants --changed-since=main test # only what changed
pants package src/python/myapp:bin
For a Python monorepo Pants 2 is nearly the default. Caching mypy/ruff results is what seals the deal.
6. Remote execution backends — BuildBuddy / EngFlow / NativeLink
Bazel, Buck2, and Pants all speak the Remote Execution API (REAPI), a gRPC standard. Cache (action result, CAS) and execution (workers) are decoupled: on a cache miss the client delegates execution to a remote worker.
Main 2026 backends.
| Product | Company | Notes |
|---|---|---|
| BuildBuddy | BuildBuddy Inc | Open-core, great UI, GCP/AWS SaaS + self-host |
| EngFlow | EngFlow | Enterprise focus, used by Spotify, Tesla |
| NativeLink | TraceMachina | OSS, Rust, still maturing but fast |
| Buildbarn | OSS | Fully open source, run it yourself |
BuildBuddy config example (.bazelrc).
build --bes_results_url=https://app.buildbuddy.io/invocation/
build --bes_backend=grpcs://remote.buildbuddy.io
build --remote_cache=grpcs://remote.buildbuddy.io
build --remote_executor=grpcs://remote.buildbuddy.io
build --remote_header=x-buildbuddy-api-key=YOUR_API_KEY
That single block turns on cache + execution + Build Event Stream (BES, web UI for tracing builds). On a big monorepo, build times often drop 5–10x.
7. Nx 20 — the JS/TS monorepo heavyweight
Nx is the JS/TS monorepo tool from Nrwl (now Nx). Stable in May 2026 is Nx 20.x. License MIT. The hosted offering is Nx Cloud (paid SaaS).
Two core wins.
- Local + remote cache: hash-keyed cache of task outputs (build, test, lint).
- affected: run only the projects impacted by a git diff.
npx create-nx-workspace@latest myorg --preset=ts
cd myorg
nx g @nx/react:app web
nx g @nx/node:app api
nx g @nx/js:lib shared
// nx.json
{
"tasksRunnerOptions": {
"default": {
"runner": "nx-cloud",
"options": {
"cacheableOperations": ["build", "lint", "test", "e2e"],
"accessToken": "YOUR_NX_CLOUD_TOKEN"
}
}
},
"namedInputs": {
"default": ["{projectRoot}/**/*"],
"production": ["default", "!{projectRoot}/**/*.spec.ts"]
}
}
nx run-many -t build # build everything
nx affected -t test # test only affected projects
nx graph # visualize project dep graph
nx release # version, changelog, publish
New in Nx 20: Atomizer (test splitting), Custom Conformance Rules (monorepo rule checks), Self-Healing CI (auto-retry + analyze failed e2e). After the Lerna acquisition the integration is essentially done — Lerna is now closer to an alias for Nx.
8. Turborepo 2 — Vercel's JS monorepo tool
Turborepo was built by Jared Palmer in 2021. Acquired by Vercel in 2022. Turborepo 2.0 (2024) rewrote the core in Rust, and 2.x is the May 2026 stable.
Versus Nx.
| Item | Nx | Turborepo |
|---|---|---|
| Philosophy | "Smart" workspace (generators, plugins, graph) | "Simple" task runner + cache |
| Config | nx.json + project.json | turbo.json |
| Code gen | Rich generators | Almost none |
| Cache | Local + Nx Cloud | Local + Remote Cache (Vercel free tier, self-hostable) |
| Distributed exec | Nx Agents | None (use CI shards) |
| Language | JS/TS focus, some polyglot | JS/TS only |
| Learning curve | Medium-high | Low |
// turbo.json
{
"$schema": "https://turbo.build/schema.json",
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": [".next/**", "!.next/cache/**", "dist/**"],
"inputs": ["src/**", "package.json", "tsconfig.json"]
},
"test": { "dependsOn": ["^build"], "outputs": ["coverage/**"] },
"lint": { "outputs": [] },
"dev": { "cache": false, "persistent": true }
}
}
turbo run build # full build (cache hits skipped)
turbo run test --filter=...[origin/main] # changed only
turbo run dev --parallel # parallel dev servers
turbo prune --scope=web # slice down for Docker build
Turborepo's killer combo is simplicity + Vercel integration. Set TURBO_TOKEN + TURBO_TEAM in CI and Vercel Remote Cache turns on automatically. For Next.js / Remix-shaped teams, Turborepo is the lowest-friction path.
9. Moon (moonrepo) — Rust-built polyglot dark horse
Moon appeared in 2022 as a Rust-based build system. Built by Miles Johnson, formerly of Lerna and Yarn. License MIT. Goal: unify JS/TS, Python, Rust, Go, Deno, and Bun under one tool.
Difference from Nx/Turborepo.
- Language-agnostic:
lang.tomlpins toolchain (Node, Deno, Bun, Rust, Python) versions. - Project graph: directory-scoped like Bazel, but configured in YAML.
- Affected: same concept as Nx.
- Mise integration: pairs with the Mise version manager to bootstrap toolchains.
# .moon/workspace.yml
projects:
- 'apps/*'
- 'packages/*'
vcs:
manager: 'git'
defaultBranch: 'main'
runner:
cacheLifetime: '7 days'
archivableTargets: ['build', 'test']
# apps/web/moon.yml
language: 'typescript'
type: 'application'
dependsOn:
- 'shared'
tasks:
build:
command: 'next build'
outputs: ['.next']
test:
command: 'vitest run'
moon run web:build
moon ci # every affected task
moon dep-graph
Moon is still small, but Rust speed + polyglot + YAML config is an attractive combo. Worth a look for teams who find Bazel heavy and Turborepo too JS-only.
10. Lerna 8 · Rush · pnpm workspaces — three lightweight JS options
There are still three lightweight JS monorepo options.
Lerna 8: acquired by Nrwl in 2022, now effectively part of Nx. For a new project just use Nx directly, but existing Lerna repos can opt into Nx's cache and affected logic.
// lerna.json
{ "version": "independent", "npmClient": "pnpm", "useNx": true }
lerna run build --since=origin/main
lerna publish
Rush (Microsoft Rush Stack): built on top of pnpm with strict policies (no phantom deps, change-file checks). Used inside Microsoft, Office Online, parts of Azure. Pick it when a big JS monorepo needs strong guardrails.
rush install
rush build
rush change # author a change file before PR
rush publish
pnpm workspaces (9.x): the simplest. pnpm-workspace.yaml + package.json workspaces field. No cache or affected, but great for small monorepos (under 20 packages).
# pnpm-workspace.yaml
packages:
- 'apps/*'
- 'packages/*'
pnpm install
pnpm -r build # build every workspace
pnpm --filter=@org/web build # one package
pnpm --filter='...[origin/main]' test # affected only (pnpm 9+)
Quick guide: small team + JS only + want simple → pnpm workspaces. Mid–large + Vercel-friendly → Turborepo. Mid–large + rich tooling/policies → Nx. Large + strict policies + MS-friendly → Rush.
11. JVM camp — Gradle 8.10 + Maven 4
The JVM has its own deep build-tool ecosystem. Stable versions in May 2026.
Gradle 8.10+: Groovy/Kotlin DSL, dependency management, multi-project. Two core cache features.
- Build cache: caches task outputs (local + remote).
- Configuration cache: caches the configuration phase itself (saves seconds-to-tens-of-seconds on big projects).
- Develocity (formerly Gradle Enterprise): remote cache + build scan + test insights SaaS.
// build.gradle.kts
plugins {
java
application
id("org.springframework.boot") version "3.4.1"
}
group = "com.example"
version = "0.1.0"
repositories { mavenCentral() }
dependencies {
implementation("org.springframework.boot:spring-boot-starter-web")
testImplementation("org.springframework.boot:spring-boot-starter-test")
}
tasks.test { useJUnitPlatform() }
./gradlew build --build-cache --configuration-cache
./gradlew :api:test
./gradlew dependencies
Maven 4 (GA in 2025): keeps pom.xml stable while strengthening the build graph, parallelism, and the daemon (mvnd). Still the first choice thanks to Spring's gravitational pull. mvnd (Maven Daemon) avoids JVM warmup, getting close to Gradle in perceived speed.
./mvnw -T 1C clean install # 1 thread per CPU core in parallel
mvnd -T 1C clean install # daemon mode
For a JVM monorepo: new projects pick Gradle + Develocity, legacy/SI projects stay with Maven 4 + mvnd.
12. sbt 1.10 · Mill — Scala camp
There's a separate Scala post, but briefly from a build perspective.
sbt 1.10: the Scala default, incremental compile is its strength. The eternal complaint is the steep learning curve.
// build.sbt
ThisBuild / scalaVersion := "3.6.0"
lazy val root = (project in file("."))
.settings(
name := "myapp",
libraryDependencies ++= Seq(
"org.typelevel" %% "cats-effect" % "3.5.4",
"org.scalatest" %% "scalatest" % "3.2.19" % Test,
)
)
Mill (Lihaoyi): a Scala/Java/JS build expressed in Scala. Cleaner and faster than sbt, in the consensus view. Not mainstream, but new Scala teams sometimes pick it.
// build.mill
package build
import mill._, scalalib._
object app extends ScalaModule {
def scalaVersion = "3.6.0"
def ivyDeps = Agg(ivy"org.typelevel::cats-effect:3.5.4")
}
A genuinely large Scala monorepo basically only has Bazel + rules_scala as the answer, but few teams ever reach that size.
13. C/C++ — CMake 3.31 + Ninja 1.12 / Meson / Bazel
C/C++ had the longest build-system warring states. The 2026 summary.
CMake 3.31 + Ninja 1.12: the de-facto standard. CMake isn't a build system — it's a build-system generator (produces Makefiles, Ninja, MSBuild). Ninja runs the result fastest.
# CMakeLists.txt
cmake_minimum_required(VERSION 3.31)
project(myapp CXX)
set(CMAKE_CXX_STANDARD 23)
add_library(mylib STATIC src/foo.cc src/bar.cc)
target_include_directories(mylib PUBLIC include)
add_executable(myapp src/main.cc)
target_link_libraries(myapp PRIVATE mylib)
cmake -S . -B build -G Ninja
cmake --build build -j
ctest --test-dir build
Meson + Ninja: CMake's cousin, cleaner syntax (Python-ish). Used by GNOME, systemd.
Bazel + rules_cc: the answer for large polyglot C/C++ monorepos. The C++ services at Google, Stripe, and Pinterest live on this path.
GNU Make: still the first pick for small projects. Lowest learning curve.
14. Rust — Cargo workspaces + cargo-nextest
A Rust monorepo is almost always fine with Cargo workspaces.
# Cargo.toml (root)
[workspace]
resolver = "2"
members = ["crates/*", "services/*"]
[workspace.dependencies]
tokio = { version = "1.42", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
cargo build --workspace
cargo test --workspace
cargo nextest run --workspace # faster test runner
cargo metadata --format-version 1 # dep graph
If you need a remote cache, sccache (Mozilla) is standard. Backends: S3, GCS, Redis, memcached, local disk.
export RUSTC_WRAPPER=sccache
export SCCACHE_BUCKET=my-sccache-bucket
cargo build --release
sccache --show-stats
When Rust joins a large polyglot stack, Bazel + rules_rust is on the table, but the onboarding cost is real. Without a company-wide Bazel standard, sccache + Cargo is almost always the answer.
15. Go — go modules + gomonorepo patterns
Go's module system is simple, which makes monorepos natural. One go.mod at the repo root, with internal/ for package boundaries.
myrepo/
go.mod
cmd/
api/main.go
worker/main.go
internal/
auth/
storage/
pkg/
publicapi/
go build ./...
go test ./...
go vet ./...
go test -count=1 -run TestFoo ./internal/auth
When caching and remote execution matter, Bazel + rules_go + gazelle is the most mature combo. Gazelle generates BUILD.bazel from go.mod.
bazel run //:gazelle
bazel run //:gazelle -- update-repos -from_file=go.mod -to_macro=deps.bzl%go_dependencies
The Go services at Pinterest, Twitter, and Coupang live on this path.
16. Mise (formerly rtx) · just · Task — lightweight task runners
Separate from build systems, there's the "call repeated commands by short names" family. Successors to shell scripts and Makefiles.
Mise (formerly rtx): asdf-compatible version manager + task runner. Written in Rust. One tool pins Node, Python, Go, Rust, Ruby, and Bun versions, and defines tasks.
# mise.toml
[tools]
node = "22"
python = "3.12"
go = "1.23"
[tasks.build]
description = "Build all"
run = "pnpm -r build"
[tasks.test]
description = "Run tests"
depends = ["build"]
run = "pnpm -r test"
mise install # install the toolchain
mise run build
mise run test
just: a modern Make. Clean syntax, no dependencies.
# justfile
default:
@just --list
build:
pnpm -r build
test: build
pnpm -r test
release version:
git tag v{{version}}
git push origin v{{version}}
Task (taskfile.dev): YAML-based task runner, written in Go.
# Taskfile.yml
version: '3'
tasks:
build:
cmds:
- pnpm -r build
test:
deps: [build]
cmds:
- pnpm -r test
These three are not graph-aware build tools — they're command shorthands. Great as the entry point even on large monorepos: just ci can call bazel test //... or nx affected -t test under the hood.
17. Remote cache — Bazel BES vs Nx Cloud vs Turborepo Remote Cache
The three tools cache remotely in different ways.
| System | Cache key | Storage | Backend |
|---|---|---|---|
| Bazel | action hash (inputs + command + env) | CAS (Content Addressable Storage) | BuildBuddy, EngFlow, Buildbarn |
| Nx Cloud | task hash (inputs + command + env) | Nx Cloud SaaS or self-host | Nx Cloud (proprietary) |
| Turborepo Remote Cache | task hash | Vercel SaaS or self-host | Vercel, Turborepo Server (OSS) |
Bazel strengths: REAPI standard, multi-backend, cache hits are reliable thanks to sandboxing.
Nx Cloud strengths: web UI, failed-build insights, Nx Agents (distributed execution) integration.
Turborepo strengths: easiest setup (two env vars), automatic Vercel CI integration.
All three offer self-hosting, but ops effort scales BuildBuddy/Buildbarn >> Nx Cloud (Helm chart) >> Turborepo Server (one Docker container).
18. Hermetic builds + content-addressed cache — the essence of reproducibility
"It worked yesterday, broken today" almost always comes from hidden inputs (env vars, system libraries, the clock, the network). The fix is hermetic builds + content-addressed caching.
Hermetic: explicitly declare an action's inputs (sources + tools + env vars) and block external access (sandbox). Bazel, Buck2, and Pants do this by default.
Content-addressed: assume that identical input hashes produce identical outputs. Any byte of input change forces a rebuild; identical inputs hit the cache.
input_hash = sha256(sources + tools + env + command)
output: stored under output_hash
cache_key: input_hash -> output_hash
Nix follows the same philosophy (different tool from Bazel, same idea). Reproducible CI/CD becomes actually achievable.
19. Container build — BuildKit / Buildah / Kaniko / Earthly
The build-systems story ends with containers because in 2026 every service ships as an OCI image, and build-system output → container image is the final step of the pipeline.
| Tool | Daemon needed | Notes |
|---|---|---|
| Docker BuildKit | Yes (dockerd) | Standard, cache mounts, SBOM, provenance |
| Buildah / Podman build | No | Daemonless, Red Hat |
| Kaniko | No | Build containers inside a container, K8s CI-friendly |
| Earthly | Uses daemon | Dockerfile + Makefile hybrid, strong caching |
| ko (Go only) | No | Pack Go binaries into distroless images instantly |
| Jib (JVM only) | No | Maven/Gradle plugin, image build without Docker |
Bazel uses rules_oci (successor to rules_docker) to build OCI images hermetically. Image layers stay byte-identical across rebuilds when inputs match.
# BUILD.bazel
load("@rules_oci//oci:defs.bzl", "oci_image")
oci_image(
name = "api_image",
base = "@distroless_base",
entrypoint = ["/api"],
tars = [":api_layer"],
)
20. Build systems at global big tech
| Company | Primary build |
|---|---|
| Blaze (internal Bazel) | |
| Meta | Buck2 |
| Microsoft | Rush + internal systems |
| Amazon | Brazil (internal) + Cargo / Maven |
| X (formerly Twitter) | Bazel (migrated from Pants) |
| Bazel (Python/Java/Go) | |
| Stripe | Bazel |
| Spotify | Bazel + EngFlow |
| Snap | Bazel |
| Dropbox | Bazel (some Python on Pants) |
| Slack | Pants (Python, TS) |
| Vercel | Turborepo |
| Shopify | Bazel (services), nx/turborepo (storefront) |
| GitHub | Internal + Bazel |
| Discord | Buck2 |
Polyglot + large scale converges on Bazel/Buck2/Pants.
21. Korean and Japanese examples
Korea
- Coupang: heavy Bazel adoption. Search, logistics, parts of iOS/Android. The C++ search infra runs on Bazel hermeticity.
- Toss / Viva Republica: frontend on Turborepo. Backend is Gradle-heavy.
- Naver: broad usage. Some Bazel in NHN/Naver Pay; many frontend teams on Turborepo or Nx.
- Kakao: Gradle/Maven-centric, frontend on Turborepo / pnpm workspaces.
- Woowa Brothers (Baemin): Gradle + Spring-centric. Frontend on Nx/Turborepo.
- Karrot (당근): pnpm workspaces + Turborepo is the standard. Some Nx adoption.
Japan
- Mercari: heavy Bazel. Go/Java microservices on Bazel + BuildBuddy.
- LINE Yahoo: Bazel (LY) and Gradle (LINE legacy). Search/ads infra on Bazel.
- CyberAgent / Ameba: Nx + Turborepo. Bazel on parts of the ad platform.
- Recruit / Indeed: heavy Bazel. Indeed has published frequently on monorepo + Bazel.
- DeNA: Gradle-centric with some Bazel.
- Rakuten: Gradle + Maven, some Bazel.
Worth noting: Japanese big tech adopts Bazel at a higher rate than Korea, with Mercari, Indeed, and LY leading.
22. When to pick what — the decision tree
Putting it all together.
- Small JS team (~5 people, ~10 packages) →
pnpm workspacesalone. - Mid-size JS/TS team (~30 people, ~50 packages) + Vercel/Next.js-heavy →
Turborepo 2+ Vercel Remote Cache. - Mid-size JS/TS team + need rich tooling/policies →
Nx 20+ Nx Cloud. - JS + Python + Go polyglot monorepo →
MoonorBazel. - Python monorepo (data/ML team) →
Pants 2. - New JVM project →
Gradle 8.10+ Develocity. - Legacy/SI JVM project →
Maven 4+mvnd. - Rust monorepo →
Cargo workspaces+sccache+cargo-nextest. - Go monorepo → single
go.mod, add Bazel + Gazelle when needed. - C/C++ →
CMake + Ninja(small) orBazel(large). - Scala →
sbtorMill, onlyBazel + rules_scalaif truly huge. - Massive polyglot + remote execution required →
Bazel+ BuildBuddy/EngFlow, orBuck2.
The most common mistake is "adopt Bazel because it's trending, regret it six months later." Only consider a polyglot build system when build time is genuinely a bottleneck, the bottleneck is solvable by remote cache/execution, and you have maintainers. Otherwise, that language's native tool is almost always correct.
References
- Bazel docs — https://bazel.build/
- Bazel Bzlmod (MODULE.bazel) — https://bazel.build/external/module
- Buck2 — https://buck2.build/
- Buck2 GitHub — https://github.com/facebook/buck2
- Pants 2 docs — https://www.pantsbuild.org/
- Nx docs — https://nx.dev/
- Nx Cloud — https://nx.app/
- Turborepo — https://turbo.build/repo
- Turborepo 2 launch — https://vercel.com/blog/turborepo-2-0
- Moon (moonrepo) — https://moonrepo.dev/
- Lerna — https://lerna.js.org/
- Rush Stack — https://rushjs.io/
- pnpm Workspaces — https://pnpm.io/workspaces
- Yarn Workspaces — https://yarnpkg.com/features/workspaces
- Gradle — https://gradle.org/
- Develocity (formerly Gradle Enterprise) — https://gradle.com/develocity/
- Apache Maven — https://maven.apache.org/
- mvnd (Maven Daemon) — https://github.com/apache/maven-mvnd
- sbt — https://www.scala-sbt.org/
- Mill — https://mill-build.org/
- CMake — https://cmake.org/
- Ninja — https://ninja-build.org/
- Meson — https://mesonbuild.com/
- BuildBuddy — https://www.buildbuddy.io/
- EngFlow — https://www.engflow.com/
- NativeLink — https://www.nativelink.com/
- Buildbarn — https://github.com/buildbarn
- Mise (formerly rtx) — https://mise.jdx.dev/
- just — https://github.com/casey/just
- Task (taskfile.dev) — https://taskfile.dev/
- Cargo Workspaces — https://doc.rust-lang.org/cargo/reference/workspaces.html
- cargo-nextest — https://nexte.st/
- sccache — https://github.com/mozilla/sccache
- Remote Execution API (REAPI) — https://github.com/bazelbuild/remote-apis
- BuildKit — https://github.com/moby/buildkit
- rules_oci — https://github.com/bazel-contrib/rules_oci
- Mercari Engineering / Bazel — https://engineering.mercari.com/en/blog/
Epilogue — "Builds are graphs, always"
If you compress 30 years of build systems into one line, it's "a directed graph from inputs to outputs — reuse the nodes whenever you can." What Make tried to do, Bazel does with sandboxing + remote execution, Turborepo does with a JS-only simplification, and Pants does in a Python-friendly way.
The tools rotate, but the principle is constant: "know the exact inputs to your build, cache the outputs, never rerun on identical inputs." 2026's polyglot build systems apply that principle across multiple languages and machines. Small teams have it covered by tools that implement the principle closest to home (pnpm + Turborepo, Cargo, Gradle); huge teams may not be able to avoid Bazel.
One last note — a build system's success depends on "who maintains the BUILD files," not the tool itself. People, not tooling. Bazel or Nx, without a dedicated build-infra owner (one or two engineers), success is roughly halved. Without the headcount budget, the right build system for that company is usually a simpler one.