Monorepo CI/CD: Caching, Affected Builds,
and Real Build Times in 2026

Most "use caching" advice skips the measured deltas. This page shows real build time numbers, the three CI strategies for monorepos, and the failure modes that kill cache hit rates.

If total CI bill is the driver: CI/CD cost calculator | CI/CD cost guide

The Three CI Strategies

1. Rebuild-everything

Run all build, test, lint tasks on every CI run regardless of what changed.

P50 build: grows linearly with package count.

When: Works fine under 5 packages. Becomes painful at 10+.

# GitHub Actions
npx nx run-many -t build test lint
npm run -w packages/* build

2. Affected-only

Use project dependency graph to only run tasks on packages affected by changed files.

Time saved scales with the share of the task graph your PR does not touch.

When: Essential at 10+ packages. Free with Nx and Turborepo.

# Nx affected
npx nx affected -t build test lint --base=origin/main

# Turborepo --filter
npx turbo run build test --filter=...[origin/main]

3. Cache-aware

Add remote cache on top of affected-only. Cache hits are returned in seconds instead of rebuild.

Cache hits return in seconds, so time saved tracks your cache-hit rate on affected tasks.

When: Essential at 30+ packages or when CI run times exceed 10 minutes.

# Nx Cloud
export NX_CLOUD_ACCESS_TOKEN=${{ secrets.NX_CLOUD_TOKEN }}
npx nx affected -t build test lint --base=origin/main

# Turbo + Vercel Remote Cache
export TURBO_TOKEN=${{ secrets.VERCEL_TOKEN }}
export TURBO_TEAM=my-team
npx turbo run build test --filter=...[origin/main]

Benchmark Numbers (Cited to Source)

19h (mono) vs 2h (poly) median PR cycle time

Source: Faros AI, 320 engineering teams

PR cycle time measures from first commit to merge. Monorepo PRs are typically larger (touch more packages), which drives the 9x delta. Build time and PR size are confounded. Faros also reports much heavier tails in monorepos (worst-case PRs past ten days), so the median understates the variance.

Why we don't quote a single "X% faster" number

Vendor case studies advertise reductions anywhere from a few percent to 90%+, but they measure different repos, workloads, and baselines, so a single portable figure would be invented precision. The reduction on your repo is bounded by two things you can measure yourself: the share of the task graph a typical PR leaves untouched (the affected-only win) and your cache-hit rate on the tasks that do run (the remote-cache win). A PR touching one leaf package in a 200-package graph skips almost everything; a change to a base library everyone depends on invalidates most of it. Instrument your own pipeline before trusting anyone's headline percentage.

Tool-Specific CI Commands

ToolAffected-only commandRemote cacheDistributed builds
Nxnpx nx affected -t buildNx Cloud (env: NX_CLOUD_ACCESS_TOKEN)Yes (Nx Agents)
Turboreponpx turbo run build --filter=...[origin/main]Vercel Remote Cache (env: TURBO_TOKEN)No
Bazelbazel build //... --build_event_stream=...BuildBuddy / EngFlow remote cacheYes (BuildFarm)
Rushrush build --to-except-point <sha>Azure Blob / S3 (custom setup)Partial
Pantspants --changed-since=main build ::Toolchain remote cacheYes (remote execution)

Common CI Failure Modes in Monorepos

Cache poisoning

High

Non-deterministic build writes wrong artifact to cache. Subsequent runs use poisoned artifact. Fix: ensure build outputs are determined only by inputs (no timestamps, random seeds, env vars not hashed).

Untracked dependencies

High

A build task depends on a file not declared in its input set. Changes to that file don't invalidate the cache. Fix: declare all file inputs explicitly in nx.json inputs or turbo.json inputs.

Cache hit rate collapse

Medium

Large packages or packages with many dependents make almost every PR's cache stale. Fix: split large packages into smaller ones; be explicit about outputs.

Partial-clone issues

Medium

git clone --depth=1 in CI causes nx affected to fail (no full history to compare against main). Fix: use fetch-depth: 0 or fetch the base branch explicitly.

Flaky tests polluting cache

Medium

A flaky test passes and caches its result. Next run returns cached 'pass'. Flakiness hidden by cache. Fix: mark known-flaky tests as no-cache; track flakiness in CI observability.

CI/CD FAQ

How do you speed up monorepo builds in CI?+
Three levels: (1) Affected-only: run only tasks affected by changed files (Nx affected, Turbo --filter with --affected). (2) Local caching: cache build outputs by content hash; skip if unchanged. (3) Remote caching: share cache hits across CI workers (Nx Cloud, Vercel Remote Cache). Each level compounds the previous.
What is Nx affected?+
Nx affected is a set of commands that use the project dependency graph to determine which projects were affected by changes since a base commit (e.g., main). nx affected:build only runs build for affected projects and their dependents. nx affected:test only runs tests for affected projects. Combined with caching, this is the primary CI performance lever in Nx monorepos.
Does Turborepo have an equivalent to Nx affected?+
Yes. Turborepo's --filter=...[main] flag uses git diff against a base branch to filter which packages run. Combined with Turbo's pipeline dependency graph, it achieves similar results to Nx affected. Syntax: npx turbo run build --filter=...[origin/main].
What is cache poisoning in a monorepo CI pipeline?+
Cache poisoning happens when a non-deterministic build writes an incorrect artifact to the cache, and subsequent CI runs use the poisoned artifact instead of rebuilding. Common causes: environment variables baked into builds, timestamp-dependent outputs, non-hermetic tests that write to shared state. Bazel's hermetic builds prevent this by design. Nx and Turbo can be poisoned if build outputs depend on environment state that isn't hashed.
Tool comparison →Migration cost →CI/CD cost calculator →