‹ Blog
Juri Strumpflohner
Juri StrumpflohnerJuri Strumpflohner

Nx 23: 4x Faster Nx Agents, Agentic Nx Migrate, improved targetDefaults, .Net GA

Nx 23 is out: smarter agentic migrations, big performance improvements across the platform, shell tab-completion, finer-grained target configuration, native TypeScript support, and a big cleanup of deprecated APIs.

AI ❤️ Monorepos Conf 2026Free

James HenryZack DeRoseVictor SavkinJohn LindquistJack HerringtonKent C. Dodds

June 23 · Half-day online event on Nx, monorepos, and AI, with talks from Kent C. Dodds, Jack Herrington, John Lindquist, and more.

Register for a chance to win a reMarkable Paper Pro Move.

Huge performance improvements across the platform

We invested into performance A LOT. Here are some of the main highlights.

Nx Agents: 4x faster, 30% cheaper than GitHub Actions

Our Nx Cloud team reworked Nx Agents with optimized resource classes and a smarter task-distribution algorithm. We ran them head-to-head against a standard GitHub Actions setup on a large, realistic monorepo (the full affected pipeline: lint, typecheck, test, build, e2e):

  • Wall-clock time dropped from 48 minutes to 12.
  • Cost per run went from $0.66 to $0.46.
  • VM minutes fell 24%.

That works out to roughly 4x faster and 30% cheaper. Read the full benchmark or connect your workspace now.

Faster Nx core

Across the recent 22.x releases, the Nx daemon got dramatically lighter. On the Nx repo itself,

  • its memory footprint dropped from around 1.5GB to roughly 200MB, about a 7x reduction.
  • Cache replay got faster too: restoring 1,110 cached tasks went from about 17 seconds to 1.16 seconds.

And caching is now worktree-aware, so switching between git worktrees no longer forces a rebuild of work you've already cached. (See the Nx 22.7 release post for the full breakdown.)

Task sandboxing

With AI agents changing more and more code in your projects, cache correctness is more important than ever. Nx has always given you the tools to configure your task inputs and outputs, but it's challenging to know whether your list is exhaustive, or overly broad. That's why we introduced an OS-level sandboxing mechanism that validates every read and write during task execution against the task's declared inputs and outputs.

Any undeclared read or write is flagged as a violation. You decide how to treat them: surface them as warnings and patch things up, or enable strict mode to fail the whole execution and keep your cache correct.

Nx Cloud task sandboxing analysis flagging undeclared file reads and writes against a task's declared inputs and outputs

What sandboxing found in the Nx codebase

We run sandboxing on the Nx codebase, and it surfaced several cases where tasks were reading files they never declared as inputs. A few of this release's cache-correctness fixes came straight out of that, like @nx/webpack and @nx/rollup builds now hashing the root tsconfig.json so changes to it correctly invalidate the cache.

Sandboxing is an Nx Cloud feature, available to everyone on our multi-tenant environment. See the sandboxing docs for setup, and the Nx 22.7 release post for a deeper walkthrough.

Smoother migrations across majors

Staying on a recent version matters. Whether it is for getting access to new features, performance improvements or important security fixes. Upgrading real-world codebases can be a lot of effort, though. That's why v23 puts serious work into making the jump less painful.

Agentic migrations

Nx has shipped migration scripts since day one. They only go so far, though, because a script can capture only what's deterministic and programmatic. It can't know your exact stack, the external libraries you use, or your one-off configs. An AI agent can. That's what we added in v23.

Migrations now come in three shapes:

  • generator-only: a deterministic code migration, run as it always has been.
  • prompt-only: a migration shipped as an AI-instruction file, for changes that can't be done deterministically. @nx/next (Next 16), @nx/nuxt (Nuxt 4), @nx/vite (Vite 8), @nx/expo (Expo 54), and @nx/vitest (Vitest 4) all ship one.
  • hybrid: the generator does the deterministic part, then an agent finishes the rest (for example @nx/storybook's Storybook 10 migration).

When a queued migration carries a prompt and you're in an interactive terminal, the agent kicks in automatically to apply it for you. It also validates the generator-only migrations, rerunning after each one to catch and fix anything left out. You can turn that validation off with --no-validate or by setting migrate.validate: false in nx.json.

nx migrate --run-migrations

Nx running agentic migrations: a queue of migrations in migrations.json processed one at a time, each handed to an AI agent that runs the generator, then validates and adjusts it against your workspace

In a non-interactive environment like CI, the agentic flow is skipped. Pass --agentic explicitly if you want it there.

This is the same agentic pattern we shipped for nx import, now applied to upgrades.

Smarter migration guidance

The recommended way to upgrade on Nx 23 and later is to run nx migrate with no arguments in an interactive terminal and let it guide you. Instead of assuming you want to jump straight to latest, Nx walks you through which version to target and which packages to migrate, aligned with our documented upgrade recommendations.

nx migrate

This guided flow is available from Nx 23 onwards. Once you're on 23, upgrading to a future major (say from 23 to a 25 down the road) is interactively guided. Workspaces still on older versions won't get the guided experience, so the first step is getting onto 23.

Crossing multiple majors. If your target is two or more majors ahead of where you are, Nx now stops and recommends stepping through one major at a time (the safer path) instead of silently making the jump. You can skip that prompt with --multi-major-mode: direct goes straight to the target, gradual takes the smallest recommended step and tells you to re-run to continue. You can also set migrate.multiMajorMode in nx.json to persist the choice across runs instead of passing the flag each time.

Choosing what to migrate. Nx now separates required migrations from optional ones. Required migrations are conservative: they only make the changes needed to update Nx itself and don't touch your application's runtime behavior, so they're safe to run often. Optional migrations update the dependencies your packages recommend and can affect task and runtime behavior, so they deserve more careful QA. Decoupling the two means you can stay on the latest Nx without being blocked on the heavier review that runtime changes need. The --include flag picks which set runs (required, optional, or all). See the Automate Updating Dependencies guide for the full picture.

Agentic nx import

Importing an existing project into a monorepo is one of those tasks that's 90% mechanical and 10% messy. nx import handles the deterministic part: it clones a repo, filters and rewrites its git history, merges it into your workspace, and detects which Nx plugins to wire up. But real-world repos have odd layouts, custom configs, and edge cases a fixed command can't anticipate. Pairing it with an AI agent closes that gap. The agent runs the deterministic pieces, then adapts around the cases the command wasn't built for.

Read more about agentic nx import.

@nx/dotnet is now generally available

@nx/dotnet graduates out of experimental in v23. If you run .NET, or a mixed JavaScript and .NET monorepo, it's now a first-class, supported way to bring those projects into Nx. (The old @nx/dotnet/plugin export is gone; an automated migration rewrites your nx.json to the bare @nx/dotnet.)

The plugin scans your .csproj, .fsproj, and .vbproj files (so C#, F#, and VB.NET are all covered) and uses a real MSBuild-based analyzer (requires .NET SDK 8.0+) to infer targets per project. build, test, publish, and pack are cached with MSBuild-derived inputs and outputs.

Because <ProjectReference> links are wired into the Nx graph, nx affected and nx graph work natively across your .NET projects.

In a JS + .NET mixed workspace this gives you one task graph over both stacks, local and remote caching and task distribution in CI.

An Nx monorepo with a .NET API in `apps/api` and a TanStack web app in `apps/web` side by side in the editor

There's no dedicated Nx project scaffolding required. You can just run dotnet new and generate a .NET project into an Nx monorepo workspace.

A spread token for target configuration

You can now use ... as a spread token to control where Nx inserts inherited default values when merging target configs. Useful when a project needs to extend a default rather than replace it.

Say your targetDefaults set the inputs for build at a global level in your nx.json:

nx.json
{
  "targetDefaults": {
    "build": {
      "inputs": ["{projectRoot}/src/**/*.ts"]
    }
  }
}

A single project that also needs its bin folder can then extend that list instead of copy-pasting it:

project.json
{
  "targets": {
    "build": {
      "inputs": ["...", "{projectRoot}/bin/**/*.ts"]
    }
  }
}

... expands in place to whatever the inherited defaults provided. The project picks up bin/**/*.ts without redeclaring src/**/*.ts, and it automatically inherits any future change to the default inputs. The same token works in package.json under an nx key. Read more about these nx.json configurations in the reference docs.

Major improvements landing in Self-Healing CI

Self-Healing CI analyzes your failing PRs and proposes fixes automatically. We shipped some major improvements:

  • Every major VCS is supported. Alongside GitHub and GitLab, it now works on Bitbucket and Azure DevOps, with in-app setup.
  • Auto-apply recommendations. Org admins get recommendations for which tasks are safe to fix and apply automatically, so the low-risk fixes can land without requiring a human in the loop.
  • Respects your git hooks. AI-proposed commits now run through your prepare-commit-msg and commit-msg hooks.
  • Teach it your conventions. Drop a .nx/SELF_HEALING.md file in your workspace to give it project-specific context and known failure patterns.

Auto-apply suggestions panel in the Nx Cloud Self-Healing CI settings

See the Self-Healing CI docs for setup, or the auto-apply deep dive for how the recommendations work.

Shell tab-completion

Nx now ships shell tab-completion. Make sure you have Nx installed globally, then run:

nx completion

With no arguments it prompts you to pick which shells to set up and wires them in for you. If you'd rather script it (or set it up in CI or your dotfiles), generate the completion for a specific shell and source it yourself:

nx completion bash >> ~/.bashrc
nx completion zsh  >> ~/.zshrc
nx completion fish > ~/.config/fish/completions/nx.fish
nx completion powershell | Out-File -Append $PROFILE

Once it's loaded, tab away: nx <TAB> lists commands and your targets, nx run <TAB> completes project:target pairs, nx build <TAB> shows the projects that actually have a build target, and nx g <TAB> completes plugins and generators.

Native Node.js for plugin and config loading

Nx now uses Node's native TypeScript stripping to load .ts plugin files and configs by default. No more ts-node or esbuild-register in the loader path for typical plugin loading.

In practice for you this means faster cold starts and one less dependency in the resolver path. If you have plugins that depend on TypeScript-only features, set NX_PREFER_NODE_STRIP_TYPES=false to fall back to the transpiler, or NX_PREFER_TS_NODE=true if you specifically need ts-node's full type-aware transpilation.

V8 compile cache

Node 22 ships a V8 compile cache that persists compilation across runs. v23 turns it on for Nx, which cuts down JIT cost on every cold invocation.

The win is most visible on short-lived commands. nx show projects and nx graph start noticeably faster, especially on machines where you bounce between projects or run Nx from many shells in parallel.

Module Federation, aligned with upstream

Module Federation has matured, and the standard module-federation.io tooling now covers most of what teams need on its own. So in v23 we're trimming back the thick layer of Nx-specific Module Federation generators and executors we used to maintain on top of it. The upside for you: you're no longer gated on Nx to keep pace with upstream Module Federation releases.

The older Nx-specific MF generators and executors are now deprecated. For React and other JS setups, the forward path is a leaner consumer/provider model (producers are federated out of the box, and you bring your own router). For Angular, we recommend @angular-architects/native-federation.

This isn't us walking away from Module Federation. Quite the contrary, Nx with Module Federation now just works. A good example is the latest Nx + Vite Module federation setup where we teamed up with Giorgio Boa. Nx no longer requires any special configuration here; it only manages caching and task orchestration. Giorgio's Vite Module Federation plugin handles the remaining tasks.

The docs walk through the setup, and there's a demo repo you can clone.

Nx + Vite Module Federation demo: a host app and a federated remote app running side by side, sharing UI components

Also, existing Nx Module Federation setups keep working, and if the leaner approach is missing something you rely on, please tell us on Discord or GitHub so we can fill the gaps together.

Multi-version plugin compliance

Nx plugins now enforce minimum supported framework versions at generation time, and they're explicit about where that floor is instead of leaving it implicit.

If you try to scaffold against a framework version that's no longer supported, the generator errors with a clear message naming the supported floor. This applies to:

  • @nx/angular
  • @nx/cypress
  • @nx/playwright
  • @nx/rspack and @nx/rsbuild

Maintaining generators that stretch across many framework versions creates a long tail of edge cases that nobody ends up running. A clearly stated floor lets us ship cleaner, faster, less bug-prone generators going forward.

The concrete floors for v23:

Toolv23 minimum
Node.js22 LTS+
TypeScript (@nx/js)≥ 5.4
Angular (@nx/angular)≥ 19
Jest (@nx/jest)≥ 30 (pinned ~30.3.0)
Vite (@nx/vite)≥ 8
Playwright (@nx/playwright)≥ 1.36
Cypress (@nx/cypress)≥ 13

These are a snapshot. Each plugin documents its own supported versions rather than listing them in one central place, so check the plugin docs for the authoritative numbers, for example the Angular version matrix and the Playwright requirements.

Stability and improvements

A lot of this cycle went into making the everyday experience more dependable.

nx watch and the daemon are reliable under churn. The native file watcher and daemon got a serious rework. nx watch now fires once per change instead of dropping events or triggering multiple times for a single edit, and the daemon no longer serves a stale project graph after heavy file churn (branch switches, generator runs, or editors doing atomic saves). The upshot: fewer "why did Nx use the wrong graph" surprises, and a watch command you can trust.

No more orphaned processes. If you've ever hit Ctrl+C on a task that spawns Docker containers or other child processes and ended up with orphans to kill by hand, that's fixed. A native bottom-up graceful shutdown walks the process tree and cleans up cleanly, so you don't need OS-specific incantations to track down leaked Docker, browser, or daemon processes.

Deterministic project graphs. The createNodes pipeline now preserves input order, so the project graph is stable across runs. Helpful if you've seen graph snapshots flicker between otherwise identical runs.

Cleaner peer-dep rewriting. The dep-checks lint rule no longer rewrites external npm package peer deps to workspace:*; only actual workspace packages get that treatment, which avoids pnpm install failures from peers like react ending up as workspace:*.

Gradle batch streaming. Gradle batch task results now stream to Nx as they finish, instead of holding everything until the whole batch completes, so the TUI feels more responsive on Gradle-heavy workspaces.

Breaking changes

v23 is a cleanup release. Most breaking changes have automated migrations: nx migrate --run-migrations should handle them for you. The list:

Node 20 dropped. Minimum Node version is now Node 22 (Node 20 entered maintenance).

Removed generators:

  • @nx/angular:ngrx
  • @nx/angular:move (use @nx/workspace:move)
  • @nx/{angular,react,next}:setup-tailwind (configure Tailwind via your framework directly)
  • The js option in component generators across @nx/react, @nx/expo, @nx/react-native, @nx/next. Components now always generate TypeScript.
  • Deprecated stylesheet options (styled-jsx, styled-module, etc.) across React, Next, Vue, Nuxt generators.

Removed executors and entry points:

  • vitest from @nx/vite. Vitest now lives in its own @nx/vitest plugin. The split began during the 22.x line and is finalized here; an automated migration rewires you.
  • Vite 8 is supported, with an automated migration converting rollupOptions to rolldownOptions. @nx/cypress bumped to Cypress 15.14 for Vite 8 component testing.
  • SVGR option in @nx/rspack (use the withSvgr wrapper; migration ships)
  • Legacy TypeScript plugin in @nx/rollup; buildLibsFromSource default aligned (migration ships)
  • @nx/angular/module-federation entry point (use the new package path; migration ships)

Removed and deprecated APIs:

  • Flat releaseTagPattern / releaseTagPrefix properties removed from nx.json release config. Migration consolidates them into the nested release.releaseTagPattern config and updates v23 defaults.
  • version.adjustSemverBumpsForZeroMajorVersion now defaults to true. For 0.x projects, breaking changes bump the minor and features bump the patch, matching the convention that 0.x is pre-stable. Set it to false to keep the old behavior.
  • releaseTag.strictPreid now defaults to true for both fixed and independent release groups (it was false for independent groups before).

Renamed flags:

  • nx watch --includeDependentProjects is now nx watch --includeDependencies. Update your scripts if you use it.

Deprecations

v23 also tightens up a number of APIs ahead of their removal in v24. Everything here still works today, so there's no rush, but it's worth knowing what to move off before the next major.

Stricter package exports. Almost all first-party Nx packages now ship a stricter exports map. The public API keeps importing from @nx/<pkg> exactly as before, but the non-public surface that used to be reachable through @nx/<pkg>/src/* deep imports now sits behind an explicit @nx/<pkg>/internal entry. For most people this is a non-event: nx migrate ships per-package migrations that rewrite known deep-import patterns to either the public entry or /internal. It matters most if you author plugins or build automation on top of Nx, where deep imports are common. This draws a clear boundary: @nx/<pkg> is the supported API, and @nx/<pkg>/internal is the "you're reaching past it, expect churn" escape hatch.

createNodesV2 renamed. The createNodesV2 family of types and exports is renamed to the canonical createNodes names. The old V2 names still work as deprecated aliases, so nothing breaks today, but new code should use the unsuffixed names.

Executors are giving way to inferred plugins. A wide set of per-tool executors are deprecated in favor of the inferred plugins that replace them: @nx/jest:jest, @nx/cypress:cypress, @nx/playwright:playwright, @nx/detox:{build,test}, @nx/webpack:{webpack,dev-server}, @nx/storybook:storybook, @nx/remix:{build,serve}, @nx/next:{build,serve}, @nx/eslint:lint, and the React, React Native, and Expo executors. Run nx g convert-to-inferred to move over; it prompts you with the plugins that have the generator available. This is also a correctness move: the same effort behind sandboxing went into getting inputs and outputs right in our plugins, so you don't have to maintain that by hand. These plugins have been stable for a long time, so the transition should be smooth, but if you hit a snag please open an issue.

Webpack and Rspack config helpers. The composePlugins, withNx, withWeb, and withReact config helpers are deprecated. They keep working in v23 but warn, pointing you at the plugin classes that replace them (NxAppWebpackPlugin, NxAppRspackPlugin, NxReactWebpackPlugin, NxReactRspackPlugin) or at nx g @nx/<bundler>:convert-to-inferred. There's no codemod, so this one is a manual edit to your webpack.config.js or rspack.config.js.

addProjectConfiguration standalone param. The standalone parameter of devkit's addProjectConfiguration is deprecated; switch to the three-argument form.

dependsOn magic strings. The legacy 'self' and 'dependencies' magic strings in dependsOn are deprecated: they still work but now emit a warning. Move to {dependencies: true} or {projects: 'self'} explicitly before Nx 24, when they'll be removed.

A few more, framework-specific:

  • ESLint v8 support is deprecated; move to ESLint v9+.
  • The Next.js withNx helper is deprecated.
  • Angular SCAM generators are deprecated.
  • The @nx/gradle/plugin-v1 entry point is deprecated; use the default @nx/gradle entry.

Community contributors

Nx is built in the open, and a big chunk of every release comes from outside the core team. Since Nx 22, more than 100 community contributors have shipped fixes and improvements across the 22.x line and into 23.

We've been labeling good-first-issue tickets with the community label. If you want to chip in, that's the best place to start.

How to update Nx

As always, updating to the latest version of Nx is straightforward:

npx nx migrate

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

npx nx migrate --run-migrations

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