Node.js 18 → Node.js 20
Node 18 to 20 is a single LTS step with no intermediate stop. Node 18 is in maintenance with a 2025 EOL, so moving to the active Node 20 LTS extends your support window. The work is updating version pins, rebuilding native modules, and clearing any removed-API fallout.
Last verified · Updated May 22, 2026
Migrating from Node 18 to 20 is a single LTS step — go directly, there is no intermediate version to stop at. Node 18 is already in maintenance, so the upgrade is mostly mechanical: bump the version pins, reinstall and rebuild native modules, and verify the suite.
Should you upgrade directly?
Yes. Node 18 → 20 is one LTS hop. Both are even-numbered LTS lines and the public APIs are largely compatible, so there is no benefit to an intermediate version. The friction is the ABI bump for native modules and a small number of deprecated APIs removed between 18 and 20.
Key differences
- node:test test runner is stable on Node 20 (it shipped experimental on 18).
- Built-in fetch graduates from experimental toward stable.
- Newer V8 with updated language and performance features.
- OpenSSL 3 behavior is the baseline; stricter crypto defaults apply.
- A handful of long-deprecated APIs are removed — surface them during inspection.
Files and patterns to inspect
- engines.node in package.json and the committed .nvmrc / .node-version.
- The node-version matrix in .github/workflows/*.yml.
- The Docker base image (FROM node:18...).
- Dependencies with native bindings (binding.gyp, prebuilt .node files).
- Code using APIs deprecated in 18 and removed by 20.
Pre-migration checklist
- Green test suite on Node 18 before starting
- Lockfile committed; dependencies audited for Node 20 engine support
- A dedicated upgrade branch
Drift between .nvmrc, the CI matrix, and the Docker base image is a top source of 'works on my machine' failures. Change all four pins (engines.node, .nvmrc, CI node-version, Dockerfile FROM) in one commit so every environment runs the same Node.
Related paths
Official sources
Backs the breaking-change and migration-step claims.
Backs the breaking-change and migration-step claims.
Copy-ready AI prompts
Structured prompts for an AI coding assistant. Inspect first, then execute incrementally, and keep a human in the review loop.
You are helping with a Node.js migration: Node.js 18 to Node.js 20. Do not edit files yet. First inspect the repository and report: 1. The current Node version targets: the "engines.node" field in package.json, the .nvmrc / .node-version file, and the node-version used in CI (.github/workflows/*.yml) and any Dockerfile base image. 2. The module system: whether package.json sets "type": "module", and the mix of .js / .cjs / .mjs files. 3. Native modules (packages with binding.gyp or install scripts) that must be rebuilt against the new ABI. 4. Usage of deprecated or removed APIs surfaced by the new major's deprecations (e.g. legacy url.parse patterns, the old assert API, removed crypto/buffer constructors). 5. The install, build, and test commands and whether the lockfile is committed. Return: a risk summary, the highest-risk files and dependencies, a suggested migration order, the commands to run before editing, and any questions that need human confirmation.
Safety: Inspection only. The agent must not modify files in this step.
Works with Claude Code, Cursor, GitHub Copilot
Perform the migration (Node.js 18 to Node.js 20) one concern at a time. Work in this order and pause for review after each: (1) bump "engines.node" in package.json and pin the new version in .nvmrc, (2) update every CI matrix entry and Docker base image to the new Node version, (3) reinstall and rebuild native modules with `npm rebuild`, (4) run the test suite and triage runtime failures, (5) replace any deprecated/removed APIs surfaced during inspection. After each step run the project's install and test commands and report results before continuing. Do not bundle unrelated refactors or upgrade dependencies that are not required by the Node bump.
Safety: Apply changes incrementally and keep each step reviewable. Rebuild native modules before assuming a failure is a code problem.
Works with Claude Code, Cursor, GitHub Copilot
Test plan
Commands
node --versionnpm cinpm rebuildnpm testnpm run build
Manual checks
- Native modules: confirm packages with native bindings load without ABI/version errors at runtime.
- Startup: boot the app on the new Node version and watch for new deprecation warnings.
- Globals: verify code relying on built-in fetch, the test runner, or other newly-stable APIs behaves the same locally and in CI.
Regression risks
- Native addons compiled against the old ABI failing to load until rebuilt.
- Deprecated APIs removed in the new major breaking code paths not covered by tests.
- Dependencies that declare an engines range excluding the new Node version.
Acceptance criteria
- Install, build, and the full test suite pass on the target Node version.
- CI, .nvmrc, and the Docker image all reference the same Node version.
- No new Node deprecation warnings appear in the test output.
Frequently asked questions
Can I go straight from Node 18 to Node 20?
Yes. Both are LTS lines and the jump is a single major step with no intermediate version. Bump your version pins, rebuild native modules, and run the suite.
Will built-in fetch change behavior I rely on from node-fetch?
Possibly in edge cases — built-in fetch follows the WHATWG spec and may differ from node-fetch on streaming, redirects, or headers. Keep your polyfill until you have tested the built-in against those paths, then remove it as a separate cleanup.