Node.js is a JavaScript runtime used to build, execute, and operate command-line tools, backend services, build systems, and modern web applications. Every project depending on Node.js also depends on a specific runtime release, on the npm client shipped with it, and on the binary interface exposed to compiled native modules.
Runtime management is an ownership problem before it is a version problem. A workstation that installs Node.js through Homebrew, a version manager, and a container image at once will eventually resolve an executable nobody intended, and the failure is usually reported as a dependency error rather than an installation error.
This document establishes a single owner for the installation and covers resolution, upgrades, version files, release lines, and the work required after the runtime changes. Keeping supported releases current provides security fixes, framework compatibility, and modern language features, but updates must be tested, because major versions may introduce breaking changes and invalidate compiled native modules. Dependency installation, auditing, and registry trust are covered in npm dependency management.
Use one clear owner for the Node.js installation. Do not mix management methods for the same active executable.
| Owner | Appropriate when | Principal trade-off |
|---|---|---|
| Homebrew | One system-wide version suffices | Upgrades follow the formula |
| Version manager | Projects need different releases | Needs shell integration |
| Containers | The runtime must be isolated | Slower local iteration |
Homebrew suits projects that converge on one release line; a version manager suits projects that pin different releases, including releases older than the current formula; containers suit work where the runtime and build toolchain must match a deployment target. Record the selected owner in project documentation, since an undocumented choice is rediscovered by every contributor during an incident.
Determine which executable the shell resolves before installing or upgrading
anything. command -v reports the path selected for the current session, and
type -a lists every match on PATH in resolution order, where the first
entry is the one that runs. Because version managers and Homebrew publish shims
and symbolic links, process.execPath is the reliable answer to where the
running runtime lives. The last two commands report the active versions;
node -v is the shorter equivalent of node --version.
command -v node
type -a node
node -p "process.execPath"
node --version
npm --versionInstall the current formula, which includes a compatible npm release. Review the release notes for the target major version before upgrading, because the formula tracks the current release line and may cross a major boundary. Verify both tools afterwards, since npm is replaced along with the runtime.
brew install node
brew upgrade nodeHomebrew also publishes versioned formulae for individual release lines. These
may be keg-only, meaning they are not linked into the prefix and are not on
PATH by default, so inspect the caveats before assuming they are active.
brew install node@22
brew info node@22npm can be upgraded independently of the runtime that ships it. Inspect the requirements of the target release before installing it globally, since an incompatible npm client is awkward to recover from. Under a version manager, an upgrade for one release does not apply to the others.
npm view npm@latest engines
npm install --global npm@latestA version manager allows projects to use different releases without replacing the system-wide runtime. Select one tool, document the choice, and remove the others, because two managers competing for the same shell initialization produce non-deterministic resolution.
| Tool | Select a release | Project version file |
|---|---|---|
nvm |
nvm install 24 then nvm use 24 |
.nvmrc |
fnm |
fnm install 24 then fnm use 24 |
.nvmrc or .node-version |
mise |
mise use node@24 |
mise.toml |
asdf |
asdf install nodejs <version> |
.tool-versions |
asdf requires the Node.js plugin, added with asdf plugin add nodejs, and
the subcommand used to pin a version differs between asdf major versions.
Consult the upstream documentation for the installed release rather than
copying commands from older guides.
Every version manager requires shell integration to switch releases per
directory, and each provides a way to set the default release used by new
shells, such as nvm alias default 24. Global packages and the npm client are
typically isolated per release, so a tool installed globally under one release
is unavailable under another.
A version file states the release a project expects and lets a version manager
select it without arguments, for example nvm use in the project directory.
nvm reads .nvmrc; .node-version is read by several tools and is the more
portable choice. Either file contains the release alone.
24
The engines field in package.json declares the runtime range a project
supports. Review it before changing the runtime.
{
"engines": {
"node": ">=22 <26",
"npm": ">=10 <12"
}
}By default npm treats engines as advisory and emits an EBADENGINE warning
when the active runtime falls outside the declared range. To make the mismatch
a hard failure, enable strict engine checking in the project .npmrc.
engine-strict=trueThe two mechanisms are complementary: the version file selects a release locally, while the range enforces the supported window for every consumer.
Node.js publishes a new major release every six months. Even-numbered majors are promoted to Long Term Support; odd-numbered majors never are and reach end of life relatively quickly.
| Phase | Meaning | Suitable for |
|---|---|---|
| Current | Newest line, receives new features | Compatibility testing |
| Active LTS | Even-numbered line under long term support | Production systems |
| Maintenance | Critical and security fixes only | Systems awaiting upgrade |
| End of Life | No further releases, including security fixes | Nothing; migrate |
Prefer an Active LTS or Maintenance release for production unless the project documents a reason to use another line. Treat the end of life date as scheduled work rather than an event to react to, because no fixes are issued after it.
Native modules are compiled against the Node.js binary interface, identified by
an ABI version. Changing the runtime major version changes that ABI and
invalidates compiled artifacts, producing a NODE_MODULE_VERSION mismatch at
require time. Inspect the ABI version and recompile native modules against it.
When the environment may hold stale artifacts, prefer the clean reinstall,
which removes node_modules before installing from the lock file.
node -p "process.versions.modules"
npm rebuild
npm ciCompiling native modules on macOS requires the Command Line Tools. If a rebuild fails with a missing compiler or missing headers, verify the developer toolchain before investigating the package.
- Select one owner for the Node.js installation and document the choice.
- Confirm the resolved executable before diagnosing a version problem.
- Prefer an Active LTS release for production systems.
- Record runtime expectations through a version file and the
enginesfield. - Track end of life dates and schedule major upgrades in advance.
- Rebuild or reinstall native modules after any runtime major version change.
- Validate builds, tests, and type checks after a runtime change.
- Remove unused version managers rather than leaving them in shell startup.
Inspect the active runtime, the npm client, and the prefix where global
packages are installed; a prefix outside the expected owner indicates a mixed
installation. When the reported version is unexpected, list every node on
PATH and print the search path, since version manager shims must precede
Homebrew paths for per-directory switching to take effect.
node --version
npm --version
npm config get prefix
type -a node
printf '%s\n' "$PATH"A require-time NODE_MODULE_VERSION error means the artifacts were compiled
for a different runtime; rebuild or reinstall from the lock file rather than
editing the dependency tree. An EBADENGINE warning means the runtime falls
outside the declared range; change the runtime rather than widening the range.
Avoid deleting lock files or caches as a first troubleshooting step. Diagnose the ownership, version, and dependency constraint causing the failure before discarding reproducibility information.
- npm dependency management
- Homebrew package operations
- Docker installation and runtime
- Xcode and Command Line Tools
- Python interpreters and installation
- Node.js documentation: https://nodejs.org/docs/latest/api/
- Node.js release schedule: https://nodejs.org/en/about/previous-releases
- npm documentation: https://docs.npmjs.com/
- Semantic Versioning: https://semver.org/