Every Node.js project depends on a specific version of the runtime. Use the wrong one and you get cryptic build errors, native modules that refuse to compile, or "works on my machine" bugs that vanish the moment a teammate pulls the branch. A Node.js version manager fixes this by letting you install, switch, and pin Node versions per project — automatically, so nobody has to think about it.

For years, **nvm** was the default answer. But two Rust-based alternatives — **fnm** and **Volta** — now offer faster shell startup, native Windows support, and smarter automatic switching. This guide compares all three, walks through setup for each, and shows you how to keep the Node version you use locally in sync with the one that builds your deployments.

## Why a version manager matters

Installing Node.js directly from the installer locks your whole machine to a single version. That falls apart the moment you work on more than one project:

- One app targets Node 18 (LTS), another needs Node 22 for a newer feature.
- A native dependency compiled against Node 20 breaks under Node 23.
- A new teammate installs the latest Node and your build suddenly fails on their machine.

A version manager keeps multiple Node versions installed side by side and activates the right one per project — usually the instant you `cd` into the directory. Your team stops debugging version drift and your CI stops being the only place the "correct" version exists.

## The three contenders at a glance

| | nvm | fnm | Volta |
|---|---|---|---|
| Written in | Bash | Rust | Rust |
| Shell startup | Slower (shell function) | Fast | Fast (shims) |
| Native Windows | No (separate nvm-windows) | Yes | Yes |
| Version file | `.nvmrc`, `.node-version` | `.nvmrc`, `.node-version` | `package.json` (`volta` field) |
| Auto-switch on `cd` | Manual / shell hook | Shell hook (`--use-on-cd`) | Automatic (transparent shims) |
| Manages more than Node | No | No | Yes (npm, yarn, pnpm, binaries) |

nvm is the incumbent everyone recognizes. fnm is the fast, drop-in replacement that still reads your existing `.nvmrc` files. Volta takes a different philosophy: it pins versions inside `package.json` so the version travels with the repo, and switches transparently without a shell hook.

## fnm: the fast nvm-compatible option

fnm ("Fast Node Manager") is built in Rust and reads the same `.nvmrc` and `.node-version` files nvm uses, which makes it a near-zero-friction switch for existing projects.

### Install fnm

```bash
# macOS / Linux
curl -fsSL https://fnm.vercel.app/install | bash

# Homebrew
brew install fnm

# Windows (winget)
winget install Schniz.fnm
```

Then activate it in your shell profile so it can switch versions automatically:

```bash
# ~/.bashrc or ~/.zshrc
eval "$(fnm env --use-on-cd)"
```

The `--use-on-cd` flag is what makes fnm switch Node versions the moment you enter a directory containing an `.nvmrc` or `.node-version` file.

### Use fnm

```bash
fnm install 20        # install a specific version
fnm use 20            # use it in the current shell
fnm default 20        # set the global default
fnm use               # read .nvmrc / .node-version in the current project
fnm list              # show installed versions
```

To pin a project, drop a version into a file at the repo root:

```bash
echo "20" > .nvmrc
```

Now anyone with fnm (or nvm) who enters the project gets Node 20.

## Volta: version pinning that travels with the repo

Volta's headline idea is different: instead of a separate dotfile, it records the pinned version directly in your `package.json`. Clone the repo, run any command, and Volta transparently uses the right version — no `.nvmrc`, no shell hook, no `cd` trigger to configure.

### Install Volta

```bash
# macOS / Linux
curl https://get.volta.sh | bash

# Homebrew
brew install volta
```

Volta installs shims (lightweight stand-ins for `node`, `npm`, `yarn`, etc.) that intercept every call and route it to the correct version automatically.

### Pin versions with Volta

```bash
volta install node@20      # install and set as your default
volta pin node@20          # write the pin into package.json
volta pin npm@10           # pin the package manager too
```

After `volta pin`, your `package.json` gains a `volta` block:

```json
{
  "volta": {
    "node": "20.11.0",
    "npm": "10.2.4"
  }
}
```

That single field is now the source of truth. Every teammate — and every tool that respects it — runs the exact same Node and npm, with nothing extra to install per project. Volta also manages `yarn`, `pnpm`, and arbitrary npm-installed binaries the same way.

## nvm: the incumbent

If you already have nvm, it still works fine — it's just slower to load and has no native Windows build (Windows users need the separate nvm-windows project). It reads `.nvmrc`:

```bash
nvm install 20
nvm use          # reads .nvmrc in the current directory
```

If startup speed or Windows support matters, fnm is the closest drop-in upgrade because it reads the same files. If you'd rather stop maintaining a dotfile at all, Volta's `package.json` pinning is the cleaner long-term move.

## Which one should you pick?

- **You already use `.nvmrc` and want it faster** → **fnm**. Same files, Rust speed, native Windows.
- **You want the pinned version committed with the project, no dotfile, no shell hook** → **Volta**.
- **You're on a team that also pins npm/yarn/pnpm versions** → **Volta**, since it manages package managers too.
- **You just want the most widely documented tool and speed isn't a concern** → **nvm** is fine.

All three achieve the same core goal: everyone on the project runs the same Node version locally. The next question is making sure your *deployment* uses that version too.

## Keeping local and deploy versions in sync with DeployHQ

Here's the part most version-manager tutorials skip. Your local tool pins Node for development — but your build server needs the same version, and it won't necessarily read your version files.

DeployHQ's build pipeline **intentionally ignores** `.nvmrc`, `.node-version`, and `.tool-versions`. During a build it temporarily renames those files so they can't override your project's configured version, then restores them afterward. This is deliberate: it keeps builds predictable regardless of which dotfiles happen to be in the repo. The full behavior is documented in [how DeployHQ handles Node version files](https://www.deployhq.com/support/build-pipelines/using-nvmrc-files).

So the version you pin with fnm or Volta locally is for your team's machines — and you set the *build* version explicitly in DeployHQ. You have two clean options:

1. **Build Configuration dropdown** — pick your Node version (8 through 25 are available) in your project's [build pipeline](https://www.deployhq.com/features/build-pipelines) settings. Match it to what you pinned locally.
2. **`.deploybuild.yaml`** — declare the version alongside your build commands so it lives in the repo:

   ```yaml
   build_languages:
     - name: "node"
       version: "20"

   build_commands:
     - description: "Install and build"
       command: "npm install \n npm run build"
       halt_on_error: true
   ```

The rule of thumb: **whatever version you pin locally (in `.nvmrc` for fnm, or the `volta` field for Volta), mirror it in your DeployHQ build configuration.** Then enable [automatic deployments](https://www.deployhq.com/features/automatic-deployments) so every push builds and ships with that exact runtime — no drift between the version your team develops on and the one that reaches production.

## Beyond Node: managing every runtime at once

If your stack isn't Node-only — a Python service, a Ruby app, a Go binary — juggling a separate manager per language gets old fast. That's where a polyglot tool earns its place: you can [manage multiple language runtimes (plus tasks and env) with mise](https://www.deployhq.com/guides/mise) from a single config file, using the same per-project switching model you get here for Node.

Ready to ship with the exact runtime your team develops on? [Create a free DeployHQ account](https://www.deployhq.com/signup) and connect your repository in minutes.