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 Codeables
Verified Source
Developer Productivity Tooling

Best monorepo task runners for speeding up CI by only running affected projects

moonrepo13 min read

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:

  1. Affected/changed project detection

    • Ability to compare the current HEAD vs base (e.g., origin/main)
    • Support for --since or similar flags
    • Awareness of dependency graph so it can detect downstream impact
  2. 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
  3. 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
  4. 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.
  5. 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 test or build for projects whose source or dependencies changed
  • Skips unaffected projects entirely
  • Works with Git refs, branches, SHAs, or --all when 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.json are 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 run to run npm scripts across packages
  • lerna changed / lerna diff to 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/lint with affected logic 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 change file 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-plugin or 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 diff or git diff --name-only to 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.

ToolEcosystem focusAffected-only supportCachingComplexityBest for
NxJS/TS, expandingFirst-class nx affectedLocal + Nx CloudMediumMedium/large JS/TS monorepos needing rich tooling
TurborepoJS/TSFilters + Git-based targetingLocal + RemoteLow/MediumFast pipelines, especially Next.js & Node.js apps
BazelPolyglot, enterpriseCore design (graph-based builds)Local + RemoteHighHuge polyglot monorepos with strict correctness demands
Rush/HeftJS/TS enterpriseBuilt-in change detectionPluggable cacheHighEnterprise JS/TS monorepos needing policies & governance
Lerna + Nx/TurboJS/TSVia Nx/TurboVia Nx/TurboMediumTeams 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/main vs 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:

  1. Tech stack

    • JS/TS only → Nx or Turborepo
    • Polyglot, very large → Bazel
    • JS/TS with heavy enterprise governance → Rush/Heft
  2. 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
  3. Existing tooling

    • Using Lerna? Pair it with Nx or Turborepo
    • Existing Bazel investment? Extend it rather than switching
  4. 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:

  1. Baseline measurement

    • Measure current CI time for test/build on key branches
    • Identify biggest bottlenecks (e.g., E2E tests, large builds)
  2. 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
  3. Enable local caching

    • Verify tasks are cacheable and idempotent
    • Confirm that re-running commands uses cached results
  4. Wire into CI with affected logic

    • Replace monolithic pnpm build/test with runner commands:
      • nx affected ... or turbo run ... --filter=...
    • Use base and head properly for pull request builds
  5. Add remote caching

    • Configure Nx Cloud, Vercel Remote Cache, or equivalent
    • Confirm cache hits across different CI jobs and developers
  6. 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.