Nx integrates your Rust crates into its project and task graph, so they benefit from the same Nx features as the rest of the workspace: task pipelines, local and remote caching, affected calculation, and distribution on CI. That's what lets you add a Rust-based application to an existing monorepo.
Example repository with JavaScript and Rust projects/juristr/nx-polyglot-rust
This article lists the main building blocks for integrating Rust projects into your Nx monorepo. Point an AI agent at this page and let it handle the setup.
Install the Rust toolchain
Section titled “Install the Rust toolchain”Pin the toolchain in the repository so contributors and CI agents resolve the same rustc and Clippy. mise drives rustup for you and installs the pinned version on checkout:
[tools]rust = "1.90.0"Any toolchain manager works. Pick one that also runs in CI, so a Rust task you cache locally and the same task rebuilt on an agent come out of the same compiler.
Declare the Cargo workspace
Section titled “Declare the Cargo workspace”Nx doesn't manage the linking of crates. The root Cargo.toml lists the members and centralizes shared versions, including the path dependencies between local crates:
[workspace]resolver = "2"members = ["apps/api", "packages/domain", "packages/store"]
[workspace.dependencies]serde = { version = "1.0.228", features = ["derive"] }domain = { path = "packages/domain" }Individual crates then pick those entries up:
[dependencies]serde.workspace = truedomain.workspace = truePath dependencies are the Cargo counterpart to workspace:* in a pnpm workspace, and they're what Nx reads to draw edges between Rust projects in the graph.
Add the Rust plugin
Section titled “Add the Rust plugin”Nx has an extensibility API that lets you develop plugins integrating new technologies into Nx. This example uses the @monodon/rust community plugin.
nx add @monodon/rustThis command registers the plugin in nx.json and gives you executors that wrap the Cargo commands. @monodon/rust:build runs cargo build, :test runs cargo test, :lint runs cargo clippy, and :run runs cargo run.
Generate a new crate with the targets already wired up:
nx g @monodon/rust:binary apps/apinx g @monodon/rust:library packages/domainFor a crate that already exists, add a project.json next to its Cargo.toml:
{ "name": "api", "projectType": "application", "targets": { "build": { "executor": "@monodon/rust:build", "outputs": ["{options.target-dir}"], "options": { "target-dir": "dist/target/api/build" } }, "test": { "executor": "@monodon/rust:test", "outputs": ["{options.target-dir}"], "options": { "target-dir": "dist/target/api/test" } }, "lint": { "executor": "@monodon/rust:lint", "outputs": ["{options.target-dir}"], "options": { "target-dir": "dist/target/api/lint" } }, "run": { "continuous": true, "executor": "@monodon/rust:run", "outputs": ["{options.target-dir}"], "options": { "target-dir": "dist/target/api/run" } } }}Copy the per-target target-dir. Cargo writes into one shared target/ directory by default, so build, test, lint, and run overwrite each other's artifacts and the cached outputs stop matching the task that produced them. Giving each target its own directory under dist/target/<project>/<target> keeps them apart.
Cache Rust tasks
Section titled “Cache Rust tasks”Rust tasks cache like any other task once you tell Nx what a Rust build depends on. Define the toolchain and lockfiles as a named input, then apply it to the executors:
{ "namedInputs": { "rust": [ "default", "{workspaceRoot}/Cargo.toml", "{workspaceRoot}/Cargo.lock", "{workspaceRoot}/rust-toolchain.toml", "{workspaceRoot}/.mise.toml" ] }, "targetDefaults": { "@monodon/rust:build": { "cache": true, "inputs": ["rust", "^rust"], "dependsOn": ["^build"] }, "@monodon/rust:test": { "cache": true, "inputs": ["rust", "^rust"], "dependsOn": ["^build"] }, "@monodon/rust:lint": { "cache": true, "inputs": ["rust", "^rust"] } }}Miss the toolchain file in that list and a Rust version bump replays stale artifacts. How caching works covers the hashing rules in full.
Run both stacks from one command
Section titled “Run both stacks from one command”With the crates in the graph, a single run covers Cargo, Vite, tsc, and ESLint in dependency order:
nx run-many -t build lint test typecheckTask pipelines cross the language boundary too, because Nx treats every task the same regardless of what it shells out to. Starting a JavaScript dev server can start the Rust service it talks to:
{ "nx": { "targets": { "dev": { "continuous": true, "dependsOn": [{ "projects": ["api"], "target": "run" }] } } }}nx dev web now builds the crate's dependencies, starts api:run, and starts the web dev server. Task pipeline configuration has the rest of the syntax.
Run Rust tasks in CI
Section titled “Run Rust tasks in CI”CI needs the Rust toolchain before it can run a Cargo task, and the same mise pin covers it:
- name: Setup Rust with mise uses: jdx/mise-action@v3
- name: Set affected SHAs uses: nrwl/nx-set-shas@v5
- name: Run Nx tasks run: npx nx affected -t lint test build typechecknx affected reads the Cargo path dependencies, so a change to packages/domain schedules the crates that depend on it and skips the rest of the repository.
To spread those tasks across machines with Nx Agents, install the toolchain on the agents as well. A launch template caches the Cargo registry and warms the dependency download:
launch-templates: linux-medium-polyglot: resource-class: 'docker_linux_amd64/medium' image: 'ubuntu22.04-node24.14-v1' init-steps: - name: Checkout uses: 'nrwl/nx-cloud-workflows/v6/workflow-steps/checkout/main.yaml'
- name: Restore Cargo cache uses: 'nrwl/nx-cloud-workflows/v6/workflow-steps/cache/main.yaml' inputs: key: 'Cargo.lock|rust-toolchain.toml|.mise.toml' paths: | ~/.cargo/git ~/.cargo/registry base-branch: 'main'
- name: Install Rust with mise uses: 'nrwl/nx-cloud-workflows/v6/workflow-steps/install-mise/main.yaml'
- name: Install dependencies uses: 'nrwl/nx-cloud-workflows/v6/workflow-steps/install-node-modules/main.yaml'
- name: Fetch Rust dependencies script: cargo fetch --lockedPoint the pipeline at that template and every agent can take any task, Cargo or JavaScript:
dte: distribute-on: 2 linux-medium-polyglotlifecycle: stop-after: - lint - test - build - typecheckThe ci-config reference lists the remaining keys.
To release the crates from the same workspace, follow using Nx Release with Rust.