01 · THE PROBLEM
One global Node is fine until the second project arrives.
The Node installer puts one runtime on the PATH. That is exactly right for one project and exactly wrong for a machine that has to build several.
The failures show up in a recognisable order:
- A project needs an older major. A native dependency was built against a different ABI, and the install fails with a compilation error that has nothing to do with your code.
- A project needs a newer one. The tooling uses syntax or a built-in API your runtime does not have, and the error message points at a file inside a dependency.
- Global packages disappear. They were installed under the previous version and are invisible to the new one, which reads as “the tool is broken”.
- Permission errors on global installs. Windows-specific, common, and the reason so many answers tell you to run the terminal as administrator. Do not.
- CI disagrees with your machine. The build passes locally and fails in the pipeline, because nothing declared which version was correct.
A version manager solves all five, because the version becomes a property of the project rather than of the computer.
02 · THE OPTIONS
Four managers, and which one to pick on Windows.
| Tool | How it works | Best for | Trade-off |
|---|---|---|---|
| fnm | Fast native binary; switches on directory change via a shell hook | Most people. Quick, cross-platform, reads .nvmrc and .node-version. | Requires a line in your shell profile to switch automatically |
| nvm-windows | Symlinks the active version into a fixed path | Familiarity, and teams that already document nvm commands | Unrelated to the Unix nvm despite the name; switching needs an elevated shell in some setups |
| Volta | Shims that resolve the version from package.json at execution time | Teams. The version is pinned in the repository and applied automatically with no hook. | Its own pinning field, so the project must adopt Volta |
| mise / asdf | Polyglot version manager for many runtimes at once | Machines that also juggle Python, Go, Java and Ruby versions | More concepts than you need if Node is your only runtime |
Uninstall the plain Node installer first. A leftover installation stays on the PATH and will silently win against your version manager, producing the single most confusing failure in this whole area: node -v reports a version you did not select.
03 · THE WALKTHROUGH
fnm on Windows, in about five minutes.
# Install the version manager
winget install Schniz.fnm
# Make PowerShell load it and switch automatically per directory
if (-not (Test-Path $PROFILE)) { New-Item -ItemType File -Path $PROFILE -Force }
Add-Content $PROFILE 'fnm env --use-on-cd | Out-String | Invoke-Expression'
# Open a new terminal, then install and select a runtime
fnm install --lts
fnm default lts-latest
fnm list
node -v
npm -v
The --use-on-cd hook is what makes this worth doing: entering a directory that declares a version switches to it, and leaving switches back.
The nvm-windows alternative
winget install CoreyButler.NVMforWindows
nvm install lts
nvm use lts
nvm list
nvm-windows does not switch automatically on directory change, so you run nvm use yourself. That is the main practical difference between the two.
Close every terminal after installing. PATH changes are read at process start, so an open shell keeps the old environment and produces contradictory results while you debug. This wastes more time than any other step here.
04 · PINNING
Put the version in the repository, not in your head.
A version manager only helps if the project says which version it wants. Three mechanisms, and you can use more than one.
1. A version file
# .nvmrc — read by nvm, fnm and most CI actions
22
# .node-version — the same idea, understood by more tools
22.11.0
Commit it. Every clone, every colleague and most CI providers will now resolve to the same runtime without being told.
2. The engines field
{
"engines": {
"node": ">=22.0.0 <23"
},
"packageManager": "pnpm@9.12.0"
}
engines documents the requirement and lets package managers warn or fail on mismatch. packageManager is the field Corepack reads, and it is the cleanest way to guarantee that everyone uses the same pnpm or Yarn version.
3. Corepack for the package manager
# Ships with Node. Enables pnpm and Yarn from the packageManager field
corepack enable
# In a project with packageManager set, this uses the pinned version
pnpm install
This removes the second half of the version problem. Pinning Node while everyone runs a different pnpm still produces lockfile churn and mysterious install differences.
05 · NATIVE MODULES
When a package needs a compiler on Windows.
Most installs are pure JavaScript. The ones that are not fail loudly, with a wall of C++ output, and the fix is nearly always the same.
Packages that build native code use node-gyp, which needs a C++ toolchain and Python. On Windows that means the Visual Studio Build Tools with the desktop C++ workload:
# Install the C++ build tools node-gyp expects
winget install Microsoft.VisualStudio.2022.BuildTools --override "--quiet --add Microsoft.VisualStudio.Workload.VCTools --includeRecommended"
# Confirm Python is available to node-gyp
python --version
- Prefer a prebuilt package. Many popular native modules now ship prebuilt binaries or have a pure-JavaScript alternative. Check before installing a compiler you will never otherwise use.
- Long paths. Deep dependency trees still hit the Windows path limit. Enable long paths in Group Policy, or keep repositories close to the drive root.
- Antivirus. Real-time scanning over node_modules is a large and invisible tax on installs. Exclude your projects directory.
- WSL2 as an escape hatch. If a package is simply hostile to Windows, building it in Linux is often faster than fixing it — see the WSL2 guide.
06 · ERRORS
Six messages and what they actually mean.
| Symptom | Cause | Fix |
|---|---|---|
| node -v shows the wrong version | A leftover plain installation earlier on PATH | Uninstall Node from Apps, then reopen the terminal |
| EPERM or EACCES on a global install | Writing into a protected directory | Let the version manager own the install location; never elevate npm |
| A global CLI vanished after switching | Globals are per version by design | Reinstall it under the new version, or use npx |
| gyp ERR! find VS | No C++ toolchain | Install the Visual Studio Build Tools with the desktop C++ workload |
| Unsupported engine warning | Runtime outside the declared engines range | Switch to the declared version rather than ignoring the warning |
| Install works, CI fails | Nothing pins the version in the repository | Commit .nvmrc and set packageManager, then have CI read them |
A clean setup, in one line: one version manager, a committed version file, Corepack enabled, and no globally installed CLIs that a project actually depends on. Everything on this page follows from those four.
07 · QUICK ANSWERS
Node on Windows, briefly.
For a single project, it is fine. For a machine that builds more than one, install a version manager instead: fnm, nvm-windows or Volta. The version then becomes a property of the project rather than of the computer, which prevents the whole class of problems where an old repository will not install.
fnm is a fast native binary that can switch versions automatically when you enter a directory containing a .nvmrc file, using a hook in your shell profile. nvm-windows symlinks the active version into a fixed path and requires you to run nvm use yourself. Both work; fnm needs less manual switching.
Commit a .nvmrc or .node-version file with the major version, and add an engines field to package.json. Add a packageManager field as well and run corepack enable, so pnpm or Yarn is pinned too. Most CI providers read these files automatically, which keeps local and pipeline builds aligned.
The install is trying to write into a protected directory. Do not fix this by running the terminal as administrator. Let a version manager own the Node installation directory instead, so global packages land in a user-writable path. Where possible, avoid global installs entirely and run tools with npx.
A dependency contains native code and needs node-gyp, which requires a C++ toolchain and Python. On Windows, install the Visual Studio Build Tools with the desktop development with C++ workload. Before that, check whether the package offers a prebuilt binary or a pure-JavaScript alternative.