‹ Blog
Juri Strumpflohner
Juri StrumpflohnerJuri Strumpflohner

Nx 24: Task-Based Affected, waitFor, Fine-Grained E2E Caching and Agent-Ready Migrations

Nx 24 is here. Besides the release itself, this post covers the bigger features that landed between Nx 23 and now, in case you missed them.

Task-based affected now the default

nx affected used to work at the project level. If your change touched a project, every task of that project ran.

Task-based affected works like caching. It uses each task's inputs and outputs to decide whether your change affects it. A PR that only edits a spec file now runs that project's test and leaves its build alone. The effect is biggest on targets with narrow inputs like build, test and e2e, which is usually where most of your CI time goes.

We shipped it as opt-in in Nx 23.3, and with Nx 24 it becomes the default. New workspaces get it right away. When you upgrade an existing workspace, the migration sets affected.legacy: true in nx.json, so you keep project-level selection until you remove it:

nx.json
{
  "affected": {
    "legacy": true
  }
}

Delete that entry to switch over. If a task shows up (or doesn't) and you're not sure why, --explain lists the tasks your change touched and the tasks that read their outputs, without running anything:

nx affected -t build --explain

The affected docs cover how Nx decides that a task is affected and what to check for targets with hand-written inputs.

Dynamic task packing and Ultracache

Task-based affected is just the first level of optimizing for CI. The next are remote caching via Ultracache and distribution with dynamic task packing.

Dynamic task packing cuts the VM minutes you pay for. It keeps a CPU and memory profile of each task and uses it to run more tasks side by side on each machine, so the same work needs fewer VM minutes. Some of our customers have already cut their VM minutes by 35 to 50%. You turn it on in your Nx Cloud workspace settings, and the docs explain how dynamic task packing works.

Ultracache removes the need to configure cache inputs and outputs by hand. It records which files each task reads in CI and hashes the task from those, so a wrong inputs entry no longer causes false cache hits. It's on by default for new workspaces on every plan, including Hobby. For an existing workspace, turn it on in the Nx Cloud workspace settings.

On our monorepo benchmark with 511 projects, the combination made CI 9.2x faster with 52% less compute. And we're already working on further compute savings.

Got a monolith? Since Ultracache works from what each task reads, it doesn't depend on how your code is structured. We ran a benchmark on a 300,000-line single-project React app: CI got 11.7x faster with 78% less compute. The full setup and numbers are in the monorepo and monolith benchmark repos.

Read all the details in our announcement blog post.

Optimize e2e test runs on CI

E2e tests are often the slowest part of CI, and agents now write more of them. Atomizer already splits one large e2e task into one task per spec file, so Nx Cloud can spread them across machines. Until now, though, each of those split tasks hashed the whole app, so an edit anywhere reran every single spec.

The reason is the dev server. A spec doesn't import the code it tests. It sends requests to a server that runs as a separate task, so Nx can't tell which part of the app a spec exercises and hashes all of it.

Nx 24 adds two directives for the top of a spec file that let Nx cache each spec based on the code it tests:

  • @nx-ignore-server-inputs
  • @nx-depends-on

If you use Ultracache, add // @nx-ignore-server-inputs and import the page or component the spec tests:

apps/shop-e2e/src/checkout.spec.ts
// @nx-ignore-server-inputs
import '@myorg/checkout/checkout-page';

import { expect, test } from '@playwright/test';
...

The directive drops the app server from the spec's hash. Nx still builds and starts the server, but the server no longer counts toward the spec's hash. Ultracache then records the files the spec read on its last CI run, including the checkout page and the modules it imports, and hashes the spec from those.

If the spec depends on code it can't import, such as external templates, stylesheets or another service, name the projects with @nx-depends-on instead:

apps/shop-e2e/src/checkout.spec.ts
// @nx-depends-on: checkout
import { expect, test } from '@playwright/test';

The spec now reruns when checkout or a project it depends on changes. An edit to the unrelated search library replays the cached result. This is coarser than what Ultracache records, but it works without Ultracache.

The docs guide covers all the details.

Wait for a dev server to be ready

An e2e task that depends on serve used to start as soon as the server process started, which is often before the server accepts requests. With Nx 24, a task can wait until a continuous task it depends on is ready by setting waitFor: "ready" on its dependsOn entry:

apps/shop-e2e/package.json
{
  "nx": {
    "targets": {
      "e2e": {
        "dependsOn": [
          { "projects": "shop", "target": "serve", "waitFor": "ready" }
        ]
      }
    }
  }
}

If you use the Nx plugins, the serve and dev targets they infer already come with a readyWhen check. For your own targets, describe in readyWhen how Nx should detect that the task is ready. You can use a url, port, command, logMatches or signal:

Ready when a GET request to the URL returns a status from 200 to 403.

apps/shop/package.json
{
  "nx": {
    "targets": {
      "serve": {
        "continuous": true,
        "readyWhen": { "url": "http://localhost:4200" }
      }
    }
  }
}

Ready when a TCP connection to the port succeeds, for example for a database container.

apps/db/package.json
{
  "nx": {
    "targets": {
      "start": {
        "continuous": true,
        "command": "docker compose up postgres",
        "readyWhen": { "port": 5432 }
      }
    }
  }
}

Ready when the command exits with code 0. Nx runs it from the workspace root.

apps/db/package.json
{
  "nx": {
    "targets": {
      "start": {
        "continuous": true,
        "command": "docker compose up postgres",
        "readyWhen": { "command": "pg_isready -h localhost -p 5432" }
      }
    }
  }
}

Ready when the text shows up in the task's output. Pass an array to wait for several strings.

apps/api/package.json
{
  "nx": {
    "targets": {
      "serve": {
        "continuous": true,
        "readyWhen": { "logMatches": "Listening at http://localhost:3333" }
      }
    }
  }
}

Ready when the server reports it from its own code.

apps/api/package.json
{
  "nx": {
    "targets": {
      "serve": {
        "continuous": true,
        "readyWhen": { "signal": true }
      }
    }
  }
}
apps/api/src/main.ts
import { emitReadySignal } from '@nx/js/ready-signal';

const server = app.listen(port, () => {
  emitReadySignal(server);
});

Inferred Cypress and Playwright e2e tasks now wait for the dev server to be ready by default.

All options are in the project configuration reference.

Refreshed Node.js apps

With Nx 24, new Node.js apps build with tsc, or with the new @nx/rolldown plugin in workspaces that use TypeScript path aliases. nx serve runs the TypeScript source directly with node --watch and restarts on changes without a build step (this needs Node.js 22.18 or later).

The serve task comes from the first two lines of src/main.ts:

apps/api/src/main.ts
#!/usr/bin/env node
// @nx-target-name: serve

The new @nx/js/node plugin picks up files whose shebang names node and uses @nx-target-name as the task name. That works for any script in your workspace, so a scripts/seed-db.ts with a node shebang becomes a task too.

To create a new app:

nx g @nx/node:app apps/api

See the Node.js docs for the details.

Agentic migrations by default

Migrations work best with an agent. Nx's migration scripts make the deterministic changes, and the agent fixes what they miss because of your workspace's specific setup.

In Nx 24, migrations run as resumable steps with a runbook the agent follows, so an agent can run and verify each one.

After running nx migrate latest, when you run:

nx migrate --run-migrations

...Nx asks whether an AI agent should run the migrations.

nx migrate --run-migrations asking whether to enable the agentic flow, with options to use an AI agent this time, always, or not at all

You can also ask your agent to run the migration from an existing session. It works the same way.

Claude Code asked to upgrade to the latest Nx 24 runs nx migrate, then reads the runbook Nx wrote for the migrate run

The automate updating dependencies guide and the advanced update guide cover the details.

Other agentic improvements

A few more changes since Nx 23 help when an agent runs Nx.

Agentic nx import. The same pattern also works for importing repositories. nx import handles the deterministic part (cloning, rewriting git history, merging, detecting plugins), and an agent takes care of the odd layouts and custom configs a fixed command can't anticipate. The agentic nx import post walks through it.

Sandboxes. Coding agents often run in a sandbox that restricts file and socket access to an allowlist. nx configure-ai-agents writes the allowances Claude Code and other coding agents need for Nx's sockets, so Nx works inside those sandboxes (sandboxing guide).

Agent-friendly output. Nx keeps its output short when an agent runs it. Successful and cached tasks collapse to a single line and only failures print in full, which took an all-green test run on the Nx repo from 205 KB of output to 3 KB. When Nx detects an AI agent, it picks the summary output style, which prints only the task counts and the log file path of each failure:

nx run-many -t test --output-style=summary

Claude Code runs nx run-many -t test, gets back only the task counts and the log path of each failure, and reads the first failing log

To set up Nx for your agent, check the AI setup guide and Enhance your AI agent.

Oxlint and Oxfmt

Nx supports both Oxlint and Oxfmt from the Oxc toolchain. The @nx/oxlint plugin infers a cacheable lint task per project, can run next to ESLint while you port things over, and brings Nx's enforce-module-boundaries rule to Oxlint:

nx add @nx/oxlint

When a command lints several projects, like nx affected -t lint, Nx starts a single Oxlint process for all of them and still keeps a result and cache entry per project. On a workspace with 50 libraries, that took nx run-many -t lint from 16.5s down to 3.0s. See the Oxlint docs.

nx format detects your formatter from its config file, so it runs Oxfmt when you use it. To set it up:

nx g @nx/js:init --formatter=oxfmt

The code formatting reference has the details.

Polyglot monorepos

Nx has built-in support for .NET and Java. Both come as Nx plugins. @nx/dotnet adds your .NET projects to the project graph, and @nx/maven and @nx/gradle do the same for Java, so caching and nx affected work across languages.

Any other language works the same way. The community maintains Monodon for Rust and @nxlv/python for Python. If your language isn't covered, have an agent write the plugin instead of waiting for the Nx team to ship one.

To see it in practice, read Craigory's post on full-stack type safety with .NET and OpenAPI and mine on adding Rust to a TanStack monorepo. More is coming!

Faster project graph creation

Nx 24 lowers CPU and memory use during graph creation in @nx/rollup, @nx/vite, @nx/js/typescript, @nx/storybook, @nx/rspack and @nx/remix. This adds to the work in 23.3, where project graph creation in a large enterprise workspace got up to 60% faster on cold runs and up to 80% faster on warm runs.

Breaking changes

Nx 24 removes what we deprecated in Nx 23. Most of these changes ship with a migration, so run nx migrate latest followed by nx migrate --run-migrations and you shouldn't need to touch much by hand.

Deprecated executors removed. Nx 24 removes 35 built-in executors across 14 plugins that we deprecated in Nx 23. These are executors the Nx team maintains in its own plugins, and each one has an inferred task that replaces it. The executor API itself stays, so executors you wrote yourself with @nx/devkit, and those from community plugins, keep working as before.

PluginRemoved executors
@nx/cypresscypress
@nx/detoxbuild, test
@nx/eslintlint
@nx/expobuild, export, install, prebuild, run, serve, start, submit
@nx/jestjest
@nx/nextbuild, server
@nx/playwrightplaywright
@nx/react-nativebuild-android, build-ios, bundle, pod-install, run-android, run-ios, start, upgrade
@nx/rolluprollup
@nx/rspackrspack, dev-server
@nx/storybookstorybook, build
@nx/vitebuild, dev-server, preview-server
@nx/vitesttest
@nx/webpackwebpack, dev-server

The migration converts the targets that use them to inferred tasks and moves their options into the tool's config file. If you want to do this step at your own pace, run npx nx g infer-targets on Nx 23 before upgrading. The removed executors reference also lists the removed programmatic exports and the executors that stay, and Look Mum, No Executors tells the whole story.

Config helpers removed. composePlugins, withNx, withWeb and withReact (webpack, Rspack, Next.js) as well as nxViteTsPaths and nxCopyAssetsPlugin (Vite) are gone. The migrations rewrite your configs.

dependsOn with self and dependencies. Nx 24 reads projects: 'self' and projects: 'dependencies' as project names. Run nx repair to rewrite them to { "target": "codegen" } and { "dependencies": true, "target": "build" }.

Module Federation removed. We removed Nx's own Module Federation support, including the React and Angular host and remote generators, in favor of the official plugins. See Migrate off @nx/module-federation.

Remix deprecated. @nx/remix is deprecated and goes away in Nx 25. A migration moves your apps to React Router v7.

Generators always add inference plugins. We removed the addPlugin option, so generators register their plugin and no longer write executor targets. If you call Nx generators from your own code, remove addPlugin from those calls. We also removed the @nx/react:setup-ssr generator and the Angular SCAM generators (scam, scam-directive, scam-pipe, scam-to-standalone). Use @nx/angular:component, directive or pipe instead.

Fewer plugins infer typecheck. @nx/vite/plugin (except for Vue), @nx/rsbuild, @nx/remix/plugin and @nx/react/router-plugin no longer infer a typecheck target. Only @nx/js/typescript infers it now, and the migration registers that plugin for you.

cache in target defaults. When both an executor key and a target-name key in targetDefaults match a target, Nx no longer reads cache from the target-name key. Nx has warned about this since 23.2. Set cache on the executor key instead:

nx.json
"targetDefaults": {
  "@nx/angular:webpack-browser": { "inputs": ["production", "^production"], "cache": true }
}

nx:run-commands env files. Direct mode, which is the default, now builds the environment the same way forked mode does. As a result, envFile values read the project's .env and no longer the root one. If apps/api/.env sets PORT=5000 and the root .env sets PORT=3000, API_URL=http://localhost:${PORT} now resolves to port 5000 for nx serve api.

Smaller removals. nx watch --includeDependentProjects is gone, so use --includeDependencies. @nx/vite/plugin, @nx/js/typescript, @nx/storybook/plugin, @nx/rspack/plugin and @nx/remix/plugin no longer export createDependencies. Custom hashers on executors are deprecated and go away in Nx 25, and a migration lists the ones you use. @nx/dotnet no longer infers a restore target, so replace nx run-many -t restore with dotnet restore. If you're still on @nx/gradle/plugin-v1, a migration moves you to @nx/gradle. The @nx/playwright:wait-for-webserver change is covered in its section above.

The Nx 24 changelog has the complete list, including pnpm pruning and project-name edge cases, and the GitHub release notes list every fix.

Updates from Nx Cloud

Some more adjacent updates from Nx Cloud you might have missed.

Nx Agents are now called workers

These days, "agent" usually means an AI agent, so the machines that run your distributed CI tasks are now called "workers". Only the name changed and your CI keeps working as it is. The new commands are nx-cloud start-workers and nx-cloud stop-all-workers. The old names still work and print a deprecation warning.

.github/workflows/ci.yml
jobs:
  main:
    runs-on: ubuntu-latest
    steps:
      # ... checkout
      - run: npx nx-cloud start-workers
      # ... set up Node.js, install dependencies, and set SHAs
      - run: npx nx affected -t lint test build

The set up CI guide walks through the full setup.

Public API v1 released

The Nx Cloud Public API is now available on every plan, with stable v1 routes. The new nx-cloud api command reads it with your workspace's credentials, which is handy when an AI agent needs to look up CI data (docs).

The Nx Cloud October update in the changelog lists everything else that landed.

The CI timeline view

When hundreds of tasks run across many machines, it gets hard to see which ones were slow and what held them up. Every CI pipeline execution in Nx Cloud now has a timeline that draws each task on the worker that ran it. Failed tasks keep their position, and the task drawer shows every attempt with its own terminal output. The Dependencies tab shows which upstream tasks a late task was waiting on. Each worker also shows its CPU and memory utilization, and you can watch the timeline fill in while a run is still going.

A timeline view of a CI run with tasks packed evenly across worker lanes

The timeline view post gives you the full tour.

Thanks to the community

Thanks to the outside contributors who shipped fixes in Nx 24:

  • @christopher-buss made batch workers disconnect once they report results (#37167).
  • @minijus allowed Vitest 5 when Angular packages peer it (#37394).
  • @comp615 made externalDependencies resolve deterministically when several versions are installed (#37327).
  • @cestercian fixed the Angular Rspack SSR engine manifest being dropped as side-effect free (#37471).

Since Nx 23, more than 75 outside contributors have landed changes in Nx. A huge thanks to all of you!

If you've been meaning to contribute, the community label is a good place to start.

How to update Nx

To update Nx, run:

npx nx migrate latest

This analyzes your workspace and creates a migration file with all necessary updates. Review the changes, then apply them:

npx nx migrate --run-migrations

If you're on the Nx plugin for Angular (@nx/angular), nx migrate also updates Angular to 22.2. It skips that update in workspaces that use Cypress component testing or webpack Module Federation in an Angular project, since those break with the webpack versions Angular 22.2 installs.

New to Nx? Create a fresh workspace with npx create-nx-workspace@latest, or run nx init inside an existing npm/pnpm workspace to start using Nx with it. Head over to the Getting Started guide for a walkthrough.

Learn more