← all articles
environment setup

Node.js Environment Setup

Install Node.js the way that won't bite you later — with a version manager, sane global-package hygiene, and per-project version pinning.

3 min read·July 8, 2026

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.

tip

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 | bash

Restart your terminal, then confirm it's on your PATH:

command -v nvm

If 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 -v

Pin the Node version per project

Add a .nvmrc file to the root of each project:

node -v > .nvmrc

Now anyone on the team (or a CI runner) can run:

nvm use

and 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:

ToolWhy people switch
npmAlready installed, huge ecosystem, good enough for most projects.
pnpmMuch faster installs, disk-space-efficient (shared package store) — great for monorepos.
yarnSimilar 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

warning

"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.

warning

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.