‹ Blog
Juri Strumpflohner
Juri StrumpflohnerJuri Strumpflohner

Nx 23.2 Is Here: Oxlint, Oxfmt, Leaner CLI Output and Better Caching

I've been really looking forward to this release! This one brings a personal favorite: the Oxc toolchain arrived in Nx with @nx/oxlint and oxfmt. But there's more, let's dive in!

Oxlint and Oxfmt land in Nx

Like many of us, I've been a big fan of the Oxc toolchain and have been pushing it internally at Nx quite a bit.

In 23.2 we're landing Oxlint and Oxfmt.

The @nx/oxlint plugin

We're shipping this new plugin as experimental because we want to see you all use it and gather feedback, and also because we have further plans to improve it.

It brings an inference plugin that detects Oxlint from your config files, a configuration generator, and a bridge that runs Nx's enforce-module-boundaries rule under Oxlint's JS-plugin API.

To start using it, add the plugin:

nx add @nx/oxlint

That installs oxlint if you don't have it, registers the plugin, and writes a root .oxlintrc.json when the workspace has no Oxlint config yet. From there you get an inferred, cacheable lint task per project. The plugin needs Oxlint 1.70.0 or later.

You don't have to switch everything at once. Oxlint is designed to run next to ESLint. When you install the plugin it claims the first available target name (if not owned already), walking the list lint, oxlint, oxlint:lint, oxlint-lint. For example: a workspace with no other linter gets lint, a workspace where ESLint already owns lint gets oxlint, and you can run both side by side while you port things over.

ESLint still has a role

You'll most likely keep ESLint around for a while. Right now Oxlint doesn't parse rules that read JSON, HTML or Angular templates as there is no parser, so those rules will need to stay on ESLint for the time being.

The inferred target is available across the workspace as soon as you run nx add. The configuration generator adds project-specific plugins when a project needs them. For example, a React project can extend the root config with the React rules:

nx g @nx/oxlint:configuration --project=my-app --plugins=react

What about performance?

This is not a formal perf test, but I gave it a spin on a monorepo with 67 lintable projects.

RunTime
ESLint baseline17.93s
Oxlint, native rules2.74s
Oxlint with Nx boundaries14.93s

You can see that the boundary rules account for a lot of time here as they run over Oxlint's JS API bridge. There's some possibility to improve this by doing work batching which is something we're currently looking into.

Built-in Oxfmt support

Prettier used to be hardwired into Nx via the nx format command. Starting with this release Nx now detects which formatter is being used directly from the config files: Prettier or Oxfmt.

To configure Oxfmt, run the @nx/js init generator:

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

That adds the dependency and writes an .oxfmtrc.json with singleQuote set. After that nx format:write and nx format:check just use it. Oxfmt's defaults deviate slightly from Prettier's, so it is worth a look at the generated .oxfmtrc.json if you care about matching your previous output.

If both are configured, Oxfmt wins, and Nx warns once so the choice isn't silent. And having no formatter configured is no longer an error: nx format warns and exits 0.

What about performance?

We ran a check on the Nx repository and saw roughly an 18-20x improvement between Oxfmt checks (e.g. nx format:check) and writes. Keep in mind that this isn't a scientifically set up perf measurement, so take the numbers with a grain of salt.

Reducing the log output for better readability and AI token friendliness

Conciseness is king! That's important for humans, and even more so for agents. More output means bigger token cost as AI agents need to re-read and process it.

So we made a couple of quality-of-life improvements. Successful and cached tasks now collapse to a single line, and only failures print their full output.

Terminal output of an Nx run-many test run where every cached success collapses to a single check-marked line, the one failing task prints its vitest error in full, and the summary reads Failed tasks herdr-polygraph test and Output of 54 successful tasks were not shown

We measured this on the Nx codebase with nx run-many -t test over 30 plugin packages, 100% cache hits in both runs, so the only variable was rendering:

  • All-green run (31 cached successes): 205,452 bytes to 3,083 bytes, a 66.6x reduction.
  • Mixed run (32 successes, 2 failures): 326,457 bytes to 45,846 bytes, a 7.1x reduction.

If your agent runs Nx, it now gets the collapsed output automatically. The same holds in CI, where it makes GitHub Actions logs far shorter to scan. Failures still print in full, so nothing you or an agent needs to debug goes missing.

This is the static-failures-only output style, which Nx now picks by default in CI and other non-interactive runs. If you want the full output back, pass --output-style=static on a single command, or set NX_DEFAULT_OUTPUT_STYLE=static for an entire CI job.

A status bar for the terminal UI

The Nx TUI got some really cool updates this round.

The Nx terminal UI running pnpm nx run-many -t build test lint typecheck, listing 68 tasks and 158 dependencies with per-task cache and duration columns, and the full-width status bar along the bottom

There's now a full-width status bar at the bottom row. On the left there's a progress count with a live run duration (63/174 (1m 23s)) which, when clicked, opens Nx Cloud.

The Nx TUI status bar showing a 172/226 progress count with a 3.6s live run duration on the left, and the hint row on the right reading pin output 1 or 2, show output enter, filter slash

The other half is vim-style pane search. If you hit space to open the detail pane and then hit /, you search the full scrollback log, jumping incrementally as you type. This follows a Vim-style motion where hitting Enter lets you switch into n/N navigation.

The Nx TUI with the detail pane open on juriweb:build, a search for rust typed at the bottom left showing 1 out of 1 matches with enter to confirm and esc to cancel, and the matching text highlighted in the scrollback log

Tasks running inside the TUI's pty also get mouse and resize events forwarded now, so an interactive tool running under Nx behaves the way it would in your terminal.

Also, just in case you missed it from the last release, you can easily copy and paste with your mouse cursor now.

Nx and polyglot monorepos

Polyglot monorepos are becoming more common. A lot of teams realize the advantages of running AI agents end-to-end, and a lot of the maintenance burden of monorepos can now be delegated to AI agents too.

Nx + TanStack + Rust

I dug into this recently while adding Rust to a TanStack monorepo.

Nx has supported .NET for a while, and 23.2 makes @nx/dotnet better at caching.

Microsoft.Extensions.ApiDescription.Server writes an OpenAPI document at build time into whatever <OpenApiDocumentsDirectory> points at. Nx did not know those documents were build outputs, so anything consuming them, an OpenAPI-to-TypeScript codegen target for example, came up empty on a fresh clone or a CI agent whenever build replayed from cache. The MSBuild analyzer now infers them and declares them as build outputs, so the manual outputs override in nx.json is no longer needed (#36788).

Craigory from our team also did a deep-dive into how you can implement fullstack type safety in an Nx monorepo with a .NET backend and a JavaScript based frontend. Read the full blog post here.

Running Nx inside agent sandboxes

Coding agents frequently use a sandbox where filesystem and socket access is restricted to an allowlist. That led to a couple of issues because Nx opens unix sockets for the daemon, the plugin workers and forked tasks. So those tasks failed.

nx configure-ai-agents now writes the allowances Claude Code and other harnesses require, covering the Nx socket roots and read and write access on them.

Better caching across sandboxes, worktrees and repository clones

Nx cache is now readable and writeable not only across worktrees but also repository clones and agent sandboxes.

Worktrees previously shared the main checkout's .nx directory at a path unique to each machine. This worked for worktrees, but failed when trying to provide a common allowance for agent sandboxes.

We now moved the Nx cache and workspace data to ~/.nx which gives a unique path and stable cache key across multiple locations on your machine. So no more cold starts.

Running migrations one at a time

In v23 we shipped a series of improvements to Nx migrations, including being able to let agents jump in and help out. We have some really amazing improvements on the agentic migration front, but they're not yet ready for prime time, so stay tuned.

Meanwhile though, here's a small quality of life improvement by allowing you to run individual migrations more easily:

nx migrate --run-migration=@nx/react:update-23-2-0-add-svgr-webpack-if-used

A bare migration name works too when it's unambiguous, and you get an error listing the matches when it isn't.

Angular 22.1 and framework updates

Nx 23.2 supports Angular 22.1. If you're on the Nx plugin for Angular (@nx/angular), nx migrate latest handles the version bump for you.

Alongside it, angular-rspack got some fixes worth calling out:

  • builds are faster and better aligned with the esbuild application builder
  • components rebuild correctly when their templates or styles change on Windows.
  • builds no longer crash on a non-array styleUrls.
  • Cypress component testing works on Angular 22.1 again.
  • Webpack-related packages moved to optional peer dependencies, so you don't pull them in if you're not on webpack.

Vitest across the generators

To be fair, our Vitest support across generators was a bit wonky. Many generators didn't allow Vitest as the test runner.

This is now fixed: Vitest is selectable everywhere the underlying generator actually supports it. The Node, Nest, and Express generators got vitest support too, and @nx/plugin can generate vitest-based e2e tests.

nx g @nx/node:app my-api --unitTestRunner=vitest

Better Bun support

We got some really cool Bun contributions this round, including from Jarred Sumner himself.

  • @cogwirrel added Bun dependency-catalog support (#36434). Nx already resolved catalog: references for pnpm and yarn, but returned the raw unresolved string for Bun, which made generators like @nx/vitest:configuration crash outright with Cannot read properties of null (reading 'version') in any Bun workspace that catalogued its dependencies. Catalog references now resolve in generators, migrations, release versioning, lockfile pruning, and lint dependency checks. The implementation follows Bun's actual semantics rather than pnpm's, verified empirically: catalog locations are all-or-nothing, and default is not special the way it is in pnpm.

  • @Jarred-Sumner, who builds Bun, fixed bun.lock parsing for lockfile versions 2 and 3 (#36666).

  • @theescodes fixed module federation for Bun by stripping the version suffix from npm dependency names (#34960).

Miscellaneous other improvements

nx release understands selectors

--projects used to accept exact project names only. It now expands through the same project-matching logic as nx run-many, so tags, globs, directory patterns, exclusions, and fuzzy name matches all work. A selector matching nothing gives you the friendly zero-match error instead of an internal bug error.

nx release 1.2.3 --projects=tag:type:api

Faster convert-to-inferred migrations

convert-to-inferred helps you migrate to the new Nx inferred syntax vs explicit executors. Migrating is worth doing, since it removes the duplicate configuration you otherwise maintain in both project.json and the tool's own config file.

The migration used to re-run whole-workspace inference once per migrated project, so its cost grew quadratically with workspace size. On one large customer workspace that meant a two hour migration, and we expect it to land at one to two minutes now.

Simplified and more powerful Nx Cloud configuration

Previously, when you configured Nx Agents with Nx Cloud, you had to string everything together into a single start-ci-run in your main CI workflow YAML file. We've improved this recently.

That configuration now lives in a .nx/ci-config.yaml file in your repo, and npx nx-cloud start-nx-agents is a command that takes no configuration flags at all.

.nx/ci-config.yaml
lifecycle:
  stop-after:
    - lint
    - build
    - e2e-test
  fail-after: 0

dte:
  distribute-on: .nx/workflows/distribution-config.yaml

nx-agents:
  with-env-vars:
    - NPM_TOKEN
    - SENTRY_AUTH_TOKEN

Your CI file shrinks to a single line:

.github/workflows/ci.yml
- run: npx nx-cloud start-nx-agents

Louie from our team covered the full details in his blog post: We Simplified the Nx CI Configuration.

Thanks to the community

A huge thanks to the ~25 outside contributors who shipped fixes and features across 23.1 and 23.2. The Bun work above is the clearest example, but it goes well beyond that: @ATKasem, @christopher-buss, @seungdeok, @paustint, @sanshan, @harshmathurx, @Squixx, @Roozenboom, @kevindcode, @Optischa, @prafful1234, @Tusharkhadde, @djohnson-aperture, @dbwodlf3, and more all landed changes this cycle.

If you've been meaning to contribute, the community label is a good place to start. These folks already did. You can be next.

How to update Nx

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

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

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