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.

ToolHow it worksBest forTrade-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.

POWERSHELL · INSTALL FNM AND NODE
# 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

POWERSHELL · NVM-WINDOWS
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

PROJECT ROOT
# .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

PACKAGE.JSON
{
  "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

POWERSHELL
# 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:

POWERSHELL (ADMIN)
# 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.

SymptomCauseFix
node -v shows the wrong versionA leftover plain installation earlier on PATHUninstall Node from Apps, then reopen the terminal
EPERM or EACCES on a global installWriting into a protected directoryLet the version manager own the install location; never elevate npm
A global CLI vanished after switchingGlobals are per version by designReinstall it under the new version, or use npx
gyp ERR! find VSNo C++ toolchainInstall the Visual Studio Build Tools with the desktop C++ workload
Unsupported engine warningRuntime outside the declared engines rangeSwitch to the declared version rather than ignoring the warning
Install works, CI failsNothing pins the version in the repositoryCommit .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.

Should I install Node.js from the official installer?

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.

What is the difference between nvm-windows and fnm?

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.

How do I pin a Node version for a project?

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.

Why do I get EPERM errors installing global npm packages?

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.

Why does npm install fail with C++ compiler errors?

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.