Node.js 18 → 22 Migration Guide¶
Official Changelogs¶
- Node.js 20: https://nodejs.org/en/blog/release/v20.0.0/
- Node.js 22: https://nodejs.org/en/blog/release/v22.0.0/
Key Breaking & Impactful Changes¶
1. process.exit()
/ process.exitCode
no longer coerce strings¶
Before (Node 18):
process.exit("1"); // coerced to 1
process.exitCode = "0"; // coerced to 0
After (Node 20+): These must be integers; strings, null
, or undefined
will throw an error when passed to process.exit()
, or behave unexpectedly when assigned to process.exitCode
.
process.exit(1); // ✅ correct
process.exitCode = 0; // ✅ correct
Fix: Audit and replace all usages where a non-integer (especially string) is passed to process.exit()
or assigned to process.exitCode
.
2. url.parse()
with invalid ports now emits a runtime warning¶
Before (Node 18):
const { parse } = require("url");
parse("http://example.com:badport"); // silently fails
After (Node 20+): Emits a deprecation warning. This legacy API will throw in future versions.
Fix: Replace with the URL
API, which throws properly for invalid URLs:
new URL("http://example.com:badport"); // will throw properly
3. Native modules must be rebuilt for Node.js 22¶
Node.js 22 ships with a newer version of V8 and ABI (Application Binary Interface). Native dependencies like sharp
, bcrypt
, node-sass
, etc., must be rebuilt to be compatible.
Fix:
rm -rf node_modules package-lock.json # Or yarn.lock
npm install
npm rebuild # Ensures native modules are rebuilt for the current Node.js version
4. Global fetch
, WebStreams, and FormData
are now built-in and stable¶
Node.js 18 had these as experimental. Node.js 20+ includes them as stable and native, based on the undici
library for fetch
.
Fix: If you're using libraries like node-fetch
, web-streams-polyfill
, or custom polyfills/mocks for these APIs, remove them. The native implementations should be used. Be aware of minor behavioral differences (e.g., default timeouts) compared to node-fetch
.
5. ECMAScript Module (ESM) Resolution Behavior¶
Node.js 20+ has improved ESM loader and error messages, and stricter handling of package.json
"exports"
and "type"
fields.
- Impact: Modules might resolve differently, or throw errors for previously tolerant import paths. Conditional exports in
package.json
("node"
,"import"
,"require"
) are more strictly enforced. - Stability: Experimental flags like
--experimental-json-modules
and--experimental-wasm-modules
are now stable and can be removed.
Fix: If using ESM, test your imports and module resolution carefully. Review your package.json
"type"
field and "exports"
map. Ensure all module paths work correctly.
6. npm's bin scripts in package.json¶
- Change:
npm
no longer implicitly executesbin
scripts (likeeslint
,mocha
) directly frompackage.json
scripts. - Impact: If your
package.json
scripts call local binaries withoutnpx
or a full path, they will fail. - Fix: Prepend
npx
to the command, or use the full path. - Before:
"test": "mocha"
- After:
"test": "npx mocha"
or"test": "./node_modules/.bin/mocha"
7. fs.watch()
throws on unsupported platforms¶
- Change: Calling
fs.watch()
on platforms where it's not supported now throws aTypeError
. Previously, it might have failed silently or behaved unexpectedly. - Impact: If your app code uses
fs.watch()
in an environment that doesn't fully support it, this will now cause a runtime error. - Fix: Audit code for
fs.watch()
usage. Ensure it's only used where truly needed and supported.
8. http.Server
Options Validation¶
- Change: Passing an array as options to
http.Server
(e.g.,new http.Server([])
) will now throw an error. Previously, it might have been coerced or ignored. - Impact: If you had a bug in your server instantiation where an array was accidentally passed instead of an object, it will now explicitly break.
- Fix: Ensure the options argument for
http.Server
is always a plain object.
9. Stricter Stream defaultEncoding
Validation¶
- Change:
Readable
andWritable
streams will now throw an error if an invaliddefaultEncoding
is provided during instantiation or configuration. - Impact: If your code was using an invalid or unsupported encoding string for streams, it will now fail.
- Fix: Ensure valid encoding strings (e.g.,
'utf8'
,'base64'
,'hex'
) are used fordefaultEncoding
.
10. Crypto Legacy API Deprecations¶
- Change: The legacy
crypto.createCipher()
,crypto.createDecipher()
, and directnew crypto.Hash()
andnew crypto.Hmac()
constructors are now runtime deprecated or end-of-life (EOL). - Impact: Using these methods will emit runtime deprecation warnings. They are generally considered less secure or outdated and will be removed in future major versions.
- Fix: Migrate to modern, recommended
crypto
module functions such ascrypto.createCipheriv()
,crypto.createDecipheriv()
,crypto.createHash()
, andcrypto.createHmac()
.
11. util.is*
Type-Checking Function Deprecations¶
- Change: Most
util.is*
type-checking functions (e.g.,util.isString()
,util.isNumber()
,util.isArray()
, etc.) are now runtime deprecated. Also,util.log()
andutil._extend()
are deprecated. - Impact: Using these functions will emit runtime deprecation warnings. They should be replaced with native JavaScript alternatives.
- Fix: Replace deprecated
util.is*
functions with modern JavaScript constructs: util.isString(value)
→typeof value === 'string'
util.isNumber(value)
→typeof value === 'number'
util.isArray(value)
→Array.isArray(value)
util.isObject(value)
→typeof value === 'object' && value !== null
util.isBuffer(value)
→Buffer.isBuffer(value)
util.isNull(value)
→value === null
util.isUndefined(value)
→typeof value === 'undefined'
util.isNullOrUndefined(value)
→value == null
util.log(message)
→console.log(message)
util._extend(target, source)
→Object.assign(target, source)
Behavioral Changes¶
net.autoSelectFamily
default (Node.js 20):net.connect()
andnet.createServer()
now haveautoSelectFamily
set totrue
by default, which means Node.js will attempt to connect to both IPv4 and IPv6 addresses. Most applications will see no change or benefit, but if you rely on specific address family behavior, be aware.- Stream
highWaterMark
default (Node.js 22): The defaulthighWaterMark
for streams has been increased. This means streams might buffer more data by default before backpressure signals are sent, potentially affecting memory usage or backpressure mechanisms if your application is sensitive to these defaults.
Migration Steps¶
Step 1: Update Your App Manifest¶
Update your app.yml
file to specify Node.js 22:
nodeRuntime: "nodejs22.x"
Step 2: Codebase Audit¶
Search for these patterns in your codebase:
grep -r "process.exit" .
grep -r "process.exitCode" .
grep -r "url.parse" .
grep -r "node-fetch" .
grep -r "bycrypt" . # Example for native modules
grep -r "http.createServer" .
grep -r "new Readable" .
grep -r "new Writable" .
grep -r "createCipher" .
grep -r "createDecipher" .
grep -r "new crypto.Hash" .
grep -r "new crypto.Hmac" .
grep -r "util.is" .
grep -r "util.log" .
grep -r "util._extend" .
grep -r "fs.watch" .
Review package.json
scripts for local binary calls without npx
(e.g., "eslint file.js"
instead of "npx eslint file.js"
).
Step 3: Test Locally¶
Use nvm to test your app with Node.js 22:
nvm install 22
nvm use 22
npm install # Ensure all dependencies are updated and rebuilt
npm test
Step 4: Update Dependencies¶
Rebuild native modules and update dependencies:
rm -rf node_modules package-lock.json
npm install
npm rebuild
Summary of Required Code Changes¶
Area | Change |
---|---|
process.exit() |
Use only integers |
process.exitCode |
Use only integers |
url.parse() |
Replace with new URL() |
Native modules | Rebuild under Node.js 22 |
fetch / FormData |
Remove polyfills, use native implementations |
ESM | Ensure correct exports/type, remove experimental flags |
package.json scripts |
Use npx for local binaries |
fs.watch() |
Audit and ensure usage on supported platforms |
http.Server |
Options must be an object |
Streams | Ensure valid defaultEncoding |
Crypto (legacy APIs) | Migrate to modern alternatives |
util.is* functions |
Replace with native JS type checks |
util.log , util._extend |
Replace with console.log , Object.assign() |
After completing these changes, thoroughly test your app in a development org before installing it on production orgs.