Installing Node.js from the official installer works, right up until you're
juggling two projects that need two different major versions, or you hit a
permissions wall trying to npm install -g something. A version manager
avoids both problems from day one — this guide sets you up with one instead
of a bare Node install.
Why not just install Node directly?
The single biggest reason: you will eventually need more than one Node
version on the same machine. A client's legacy project pinned to Node 18,
a new project on the latest LTS, a CI config that wants to match exactly —
a version manager switches between them in one command. Installing Node
system-wide also tends to put npm's global folder somewhere that needs
sudo to write to, which is the source of most "permission denied" npm
horror stories.
If you're on Windows, the smoothest path is WSL2 (Windows Subsystem for Linux) and then following the Linux instructions below inside it. Native Windows Node development works, but most tooling in this guide (and the wider Node ecosystem) assumes a POSIX shell.
Install nvm (Node Version Manager)
On macOS or Linux:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bashRestart your terminal, then confirm it's on your PATH:
command -v nvmIf that prints nothing, add this to your ~/.zshrc or ~/.bashrc and
restart your shell again:
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"On Windows (outside WSL2), use nvm-windows
instead — it's a separate project with the same idea, installed via a
regular .exe installer.
Install Node
Install the current LTS release — this is the right default for almost every project:
nvm install --lts
nvm use --lts
nvm alias default lts/*The alias default step matters: it makes every new terminal tab use the
LTS version automatically, instead of falling back to your system Node (or
none at all).
Verify:
node -v
npm -vPin the Node version per project
Add a .nvmrc file to the root of each project:
node -v > .nvmrcNow anyone on the team (or a CI runner) can run:
nvm useand get the exact version the project expects, read straight from
.nvmrc. Pair it with an engines field in package.json so npm warns
if someone's on the wrong major version:
{
"engines": {
"node": ">=22.0.0"
}
}Picking a package manager
npm ships with Node, so it's the default — and it's genuinely fine. Two common alternatives worth knowing:
| Tool | Why people switch |
|---|---|
| npm | Already installed, huge ecosystem, good enough for most projects. |
| pnpm | Much faster installs, disk-space-efficient (shared package store) — great for monorepos. |
| yarn | Similar speed benefits to pnpm, older/more established, "yarn workspaces" for monorepos. |
Unless a project already picked one, stick with npm until you have a concrete reason (like monorepo package hoisting pain) to switch.
Common gotchas
"command not found: nvm" in a new terminal tab. The install script edits
your shell profile, but if you use a non-default shell (fish, a custom
.zprofile setup) it may not find the right file. Check ~/.zshrc for the
NVM_DIR block shown above and add it manually if missing.
Global installs still failing with EACCES. You likely have a leftover
system Node install shadowing nvm's. Run which node — if it doesn't point
into ~/.nvm/..., uninstall the system Node package (via brew uninstall node, your Linux package manager, or Windows "Add or Remove Programs") and
open a fresh terminal.
With Node managed per-project like this, you can move between codebases without ever thinking about which Node version is "currently installed" — it's just whatever each project asks for.