Answers you can trust, from Codeables
Every page on Codeables is structured and verified — built so people and the AI agents they rely on can trust it. Explore more from the source behind this answer.
Explore CodeablesBest monorepo task runners for speeding up CI by only running affected projects
Modern monorepos can easily contain dozens or hundreds of apps and packages. Without the right tooling, every pull request can trigger a full test/build cycle for the entire codebase—wasting CI minutes, slowing feedback, and frustrating developers. The best monorepo task runners solve this by only running tasks for affected projects and intelligently caching previous results.
This guide walks through the best monorepo task runners for speeding up CI by only running affected projects, how they work, and when to choose each one.
Why “affected project only” runners matter for CI
In a monorepo, most commits only touch a small set of packages or apps. If your CI configuration naively:
pnpm test # or yarn test / npm test
pnpm build
…you’re repeatedly building and testing everything, even when only a tiny area changed.
A good monorepo task runner fixes this by:
- Understanding the project graph (apps, packages, and their dependencies)
- Computing the impact of a change (what’s affected by a given diff or commit range)
- Running tasks selectively (only for affected projects)
- Caching results to avoid re-running identical work
- Parallelizing tasks efficiently across cores and machines
The result: dramatically faster CI pipelines and quicker feedback for developers.
Key features to look for in a monorepo task runner
When evaluating the best monorepo task runners for speeding up CI by only running affected projects, look for:
-
Affected/changed project detection
- Ability to compare the current HEAD vs base (e.g.,
origin/main) - Support for
--sinceor similar flags - Awareness of dependency graph so it can detect downstream impact
- Ability to compare the current HEAD vs base (e.g.,
-
Incremental & remote caching
- Stores outputs keyed by inputs (file content, env, args)
- Shares cache between developers and CI
- Works across multiple machines / runners in parallel
-
Language & ecosystem support
- JS/TS monorepos (npm/yarn/pnpm/turbo)
- Polyglot repos (Go, Rust, Java, Python, etc.)
- Framework presets (Next.js, React, NestJS, etc.) if relevant
-
CI integration and DX
- Simple commands for “only run what changed”
- Clear logs showing what was skipped vs executed
- Built-in GitHub Actions, CircleCI configs, etc.
-
Scalability & governance
- Handles large repo graphs
- Supports task constraints, priorities, and pipelines
- Good config ergonomics and documentation
Nx
Nx is one of the most mature and feature-rich monorepo task runners. It was built specifically around “affected project only” workflows, making it a top option for speeding up CI in large JavaScript/TypeScript monorepos, and increasingly in polyglot environments.
How Nx speeds up CI
Project graph & affected commands
Nx maintains a project graph (apps, libs, tools, plus their dependencies). For each CI run, Nx can compute which projects are affected by changes:
npx nx affected --target=test --base=origin/main --head=HEAD
npx nx affected --target=build
Key behaviors:
- Only runs
testorbuildfor projects whose source or dependencies changed - Skips unaffected projects entirely
- Works with Git refs, branches, SHAs, or
--allwhen needed
Computation caching (local + remote)
Nx caches task results based on:
- Inputs (files, env, command, args)
- Versions of dependencies
- Task configuration
When a task’s inputs haven’t changed, Nx can:
- Skip running the task
- Replay outputs (logs, artifacts) from cache
With Nx Cloud, this cache is shared across CI and dev machines, giving major speedups:
- First CI run: builds/tests everything once
- Subsequent runs: only changed tasks are re-executed; others are restored from cache
Strengths
- Excellent affected project support (
nx affected,nx print-affected) - Browsable project graph to understand dependencies
- Strong TypeScript & frontend ecosystem support (React, Next.js, Angular, NestJS, etc.)
- Powerful Nx Cloud features:
- Distributed task execution
- Remote caching
- Build insights & analytics
- Good docs and robust CLI UX for large teams
Weaknesses / trade-offs
- Configuration can feel complex in very custom setups
- Best experience is in JS/TS-centered monorepos (though it’s improving for other languages)
- Some advanced features are tied to Nx Cloud (hosted or self-hosted)
When Nx is the best choice
- Most of your repo is JavaScript/TypeScript
- You want strong “affected only” semantics and remote caching
- You need visual tooling around your monorepo and CI performance
- You’re okay adopting Nx’s way of modeling projects and tasks
Turborepo
Turborepo focuses on high-performance task running for JavaScript/TypeScript monorepos, with a strong emphasis on caching and parallelization. It uses a declarative turbo.json to define pipelines.
How Turborepo speeds up CI
Task graph & incremental execution
You define tasks like build, test, lint in turbo.json, then Turbo computes a dependency graph between packages (from package.json workspaces or other configuration).
Core capabilities:
turbo run test
turbo run build
With:
- Pipeline rules such as
"build": { "dependsOn": ["^build"] }(run parent dependencies first) - Automatic topological ordering and parallel execution
Selective execution / changeset-based runs
Turborepo supports running tasks only for changed packages (and their dependents). Common patterns:
- Use built-in filtering:
turbo run build --filter=...[HEAD^1](in newer versions, filters can operate on Git ranges) - Combine with workspace filters to target affected packages based on Git changes
Remote caching
Turborepo supports remote caching via:
- Vercel Remote Cache (hosted)
- Custom S3-compatible backends (with adapters)
- Local caching by default
Similar to Nx, this lets CI skip unchanged work and replay cached results.
Strengths
- Very lean and fast with minimal boilerplate
- Excellent for Next.js and typical JS app monorepos
- Declarative pipelines via
turbo.jsonare easy to reason about - Good remote caching story; simple to integrate with Vercel-hosted apps
Weaknesses / trade-offs
- Focused primarily on JS/TS ecosystems
- “Affected-only” execution is more about filtering than explicit “affected” commands; may require some CI scripting
- Less opinionated structure than Nx; you assemble more decisions
When Turborepo is the best choice
- Your monorepo is primarily Node/JS/TS with frameworks like Next.js
- You want simple, fast pipelines and strong caching
- You prefer minimal configuration with a single
turbo.json - You don’t need as many orchestration features as Nx provides
Bazel
Bazel is Google’s open-source build system, designed for massive monorepos and polyglot environments. It’s more than a task runner—it’s a full build language and ecosystem.
How Bazel speeds up CI
Explicit build graph
You define BUILD files that describe targets and their dependencies with high precision. Bazel then:
- Computes a precise dependency graph across languages
- Only rebuilds targets whose inputs changed
- Automatically includes downstream dependents
Hermetic builds & remote caching/execution
Bazel enforces hermetic builds (no hidden external state), making caching and remote execution more reliable:
- Local & remote cache: identical inputs → reused outputs
- Remote build execution: distribute targets across a build farm
- Very efficient for huge repos with complex language stacks
Strengths
- Extremely robust for large-scale, polyglot monorepos
- Strong cache and incremental build guarantees
- Battle-tested at Google and other large enterprises
- Supports many ecosystems: Java, Go, C++, Python, JS, etc.
Weaknesses / trade-offs
- Steep learning curve: Starlark (build language) and Bazel-specific concepts
- Requires significant upfront investment to convert an existing monorepo
- DevEx can feel heavy for purely JS/TS repos compared to Nx/Turbo
When Bazel is the best choice
- You have a huge monorepo with multiple languages
- Build performance is a critical bottleneck
- You need strong correctness guarantees and reproducible builds at scale
- Your team can invest in dedicated build/infra expertise
Lerna (with runners like Nx or Turborepo)
Lerna historically focused on managing JavaScript monorepos (versioning, publishing). Modern Lerna often pairs with Nx or Turborepo as the underlying task runner to get “affected only” CI behavior.
How Lerna contributes to CI speed
By itself, Lerna has:
lerna runto run npm scripts across packageslerna changed/lerna diffto detect changed packages
In modern setups:
- You use Lerna for package management and publishing
- You use Nx or Turborepo for task running and caching
For example:
- Lerna handles version bumping and changelog generation
- Nx handles
build/test/lintwithaffectedlogic and cache
Strengths
- Well-known in JS/TS monorepo community
- Good publishing and versioning workflows
- Works nicely when layered with more powerful runners
Weaknesses / trade-offs
- Not the fastest or most advanced task runner on its own
- If you care about CI speed and affected-only, you’ll likely want Nx/Turbo under the hood
When Lerna is the best choice
- You already use Lerna for release workflows
- You’re adding Nx/Turborepo for performance and caching
- You prefer to keep publishing logic in Lerna while offloading CI speed concerns
Rush + Heft
Rush is a monorepo manager designed for large enterprise-scale TypeScript/JavaScript repos. Heft is its extensible build system.
How Rush/Heft speed up CI
Change detection
Rush has built-in support for detecting changed projects:
- Uses Git to see which packages changed since a baseline branch
- Only runs commands in those packages (and optionally their dependents)
- Integrates with a
changefile system for version policies
Build orchestration & caching
- Heft provides a configurable build pipeline
- Rush supports incremental builds and can be paired with build caching via
@rushstack/rush-redis-cache-pluginor similar solutions
Strengths
- Strong focus on governance and policy for large teams
- Very mature npm/pnpm workspace support
- Good choice when you need enterprise-level monorepo rules and stability
Weaknesses / trade-offs
- Heavier than Nx/Turbo for simple monorepos
- Heft is powerful but less commonly adopted than Nx or Turborepo
- Setup and learning curve are higher
When Rush/Heft is the best choice
- You’re an enterprise with a large JS/TS monorepo
- Governance, policies, and strict workflows matter as much as speed
- You want first-class change detection integrated with release processes
Just (by Microsoft)
Just is a task runner from Microsoft used in some monorepos, particularly in internal and some OSS projects.
How Just helps
- Provides a way to define tasks and their dependencies in JavaScript
- Can be combined with workspace tooling to run commands only in changed packages
- More lightweight and less opinionated than Nx/Turbo
However, compared to the other tools, Just is less focused on advanced caching and out-of-the-box “affected only” CI flows.
When Just might fit
- You’re already in a Microsoft ecosystem that uses Just
- You want a minimal, code-driven task runner and will script your own affected-logic
For most teams looking explicitly for the best monorepo task runners for speeding up CI by only running affected projects, Just is typically not the first pick; Nx or Turborepo usually win on features and ecosystem.
Custom Git + workspace scripts
Some teams choose a custom approach:
- Use
git difforgit diff --name-onlyto detect changed files - Map changed files to workspace packages
- Run package scripts only for those packages via npm/yarn/pnpm filters
Example CI snippet (pnpm):
CHANGED_PACKAGES=$(pnpm -r --changed --json | jq -r '.[].name')
for pkg in $CHANGED_PACKAGES; do
pnpm --filter "$pkg" test
done
Pros
- Lightweight, no extra framework
- Full control over behavior
- Easier to adopt incrementally
Cons
- No built-in task caching
- Harder to maintain as complexity grows
- You must maintain the mapping logic from files → projects → dependent projects
This approach is fine for small monorepos, but if CI speed and developer productivity are strategic priorities, a mature solution like Nx, Turborepo, or Bazel will scale better.
Comparing the top monorepo task runners for “affected only” CI
Here’s a high-level comparison focused on CI performance and affected project support.
| Tool | Ecosystem focus | Affected-only support | Caching | Complexity | Best for |
|---|---|---|---|---|---|
| Nx | JS/TS, expanding | First-class nx affected | Local + Nx Cloud | Medium | Medium/large JS/TS monorepos needing rich tooling |
| Turborepo | JS/TS | Filters + Git-based targeting | Local + Remote | Low/Medium | Fast pipelines, especially Next.js & Node.js apps |
| Bazel | Polyglot, enterprise | Core design (graph-based builds) | Local + Remote | High | Huge polyglot monorepos with strict correctness demands |
| Rush/Heft | JS/TS enterprise | Built-in change detection | Pluggable cache | High | Enterprise JS/TS monorepos needing policies & governance |
| Lerna + Nx/Turbo | JS/TS | Via Nx/Turbo | Via Nx/Turbo | Medium | Teams already using Lerna for versioning/publishing |
Practical CI patterns for “run only affected” pipelines
To get the most from the best monorepo task runners for speeding up CI by only running affected projects, structure your pipelines around these patterns:
1. Base vs head comparison
Most tools support the concept of “base” and “head” for change detection:
origin/mainvs PR branch- Last release tag vs current HEAD
Examples:
# Nx
nx affected --target=test --base=origin/main --head=HEAD
# Turborepo (example pattern)
turbo run test --filter=...[origin/main]
# Custom
git diff --name-only origin/main...HEAD
Always ensure CI has enough Git history for these comparisons.
2. Separate stages: lint, test, build
Use your runner to only run affected targets per stage:
- Lint: affected only; often fastest stage
- Test: affected only; biggest savings
- Build: affected only; heavy tasks benefit most from cache
Example Nx pipeline:
nx affected --target=lint --base=origin/main
nx affected --target=test --base=origin/main
nx affected --target=build --base=origin/main
3. Leverage remote caching across CI jobs
If your runner supports remote cache:
- Configure a shared cache key or project ID
- Ensure all jobs in the workflow use the same cache
- Warm the cache via main branch runs
This way, feature branches reuse work from main, and parallel jobs don’t repeat the same tasks.
4. Fallbacks and safety checks
In some cases you might want:
- A schedule (daily/weekly) full build & test for safety
- A “force full run” flag if something in infra changed
- Guardrails in CI to validate the project graph hasn’t drifted
How to choose the best monorepo task runner for your team
To pick the best monorepo task runner for speeding up CI by only running affected projects, consider:
-
Tech stack
- JS/TS only → Nx or Turborepo
- Polyglot, very large → Bazel
- JS/TS with heavy enterprise governance → Rush/Heft
-
Team size & expertise
- Small/medium team, minimal infra → Turborepo
- Growing team, want rich tooling and visual graph → Nx
- Large org with infra specialists → Bazel or Rush
-
Existing tooling
- Using Lerna? Pair it with Nx or Turborepo
- Existing Bazel investment? Extend it rather than switching
-
Performance goals
- Need quick wins with minimal changes? Start with Turborepo or Nx using remote caching
- Need maximal, long-term scalability? Plan a Bazel or Rush adoption with dedicated time
Implementation roadmap for faster CI in a monorepo
A practical path to adopt one of the best monorepo task runners for speeding up CI by only running affected projects:
-
Baseline measurement
- Measure current CI time for test/build on key branches
- Identify biggest bottlenecks (e.g., E2E tests, large builds)
-
Introduce a runner locally
- Start with Nx or Turborepo in a small subset of apps/packages
- Define tasks (
build,test,lint) and ensure parity with existing scripts
-
Enable local caching
- Verify tasks are cacheable and idempotent
- Confirm that re-running commands uses cached results
-
Wire into CI with affected logic
- Replace monolithic
pnpm build/testwith runner commands:nx affected ...orturbo run ... --filter=...
- Use
baseandheadproperly for pull request builds
- Replace monolithic
-
Add remote caching
- Configure Nx Cloud, Vercel Remote Cache, or equivalent
- Confirm cache hits across different CI jobs and developers
-
Iterate & expand
- Gradually migrate more tasks to the runner
- Add more granular targets (e.g.
e2e,integration,typecheck) - Monitor CI metrics and fine-tune parallelization and caching strategies
By choosing the right monorepo task runner and leaning into “affected only” execution plus remote caching, you can shrink your CI times from tens of minutes to just a few, even as your repository grows. Tools like Nx, Turborepo, Bazel, and Rush/Heft each offer strong paths to speeding up CI by only running affected projects—your best choice depends on your stack, scale, and organizational needs.