JavaScript heap out of memory (Node FATAL ERROR near heap limit)
V8 hit the ceiling it set for itself. The machine may still have free RAM — Node just refused to grow its JavaScript heap any further.
What the error looks like
FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
1: 0x… node::Abort() …
2: 0x… v8::Utils::ReportOOMFailure …
Sometimes shortened to JavaScript heap out of memory in CI logs. It is a V8 abort, not a normal Error you can try/catch.
V8 heap vs system RAM
| Limit | Who enforces it | What you see |
|---|---|---|
| V8 old-space heap | Node / V8 (--max-old-space-size) | FATAL ERROR … heap out of memory |
| Container / cgroup memory | Docker, Kubernetes, systemd | Exit 137, OOMKilled |
| Host RAM + swap | Linux OOM killer | Also 137; dmesg shows "Killed process" |
Default old-space is roughly 1.5–4 GB depending on Node version and whether the host looks "small". A 16 GB laptop can still throw heap OOM at ~2 GB because V8 never asked for more. Conversely, setting --max-old-space-size=8192 inside a 1 GB container will get you killed with 137 before V8 complains.
Raise the limit (when it is appropriate)
# One-off
node --max-old-space-size=4096 server.js
node --max-old-space-size=8192 ./node_modules/webpack/bin/webpack.js
# Sticky for child processes (npm scripts, CLIs)
export NODE_OPTIONS=--max-old-space-size=4096
npm run build
# package.json script
"build": "NODE_OPTIONS=--max-old-space-size=4096 next build"
- Values are megabytes.
4096≈ 4 GB. NODE_OPTIONSis inherited by anything that spawnsnode. Do not leave a huge value in a shared shell profile forever.- Match the heap to ~50–75% of the container/VM memory so the process has room for native buffers, stacks and the OS.
When raising the limit is the wrong fix
- Memory leak — usage climbs over hours/days until crash. Restarting or bumping the heap only delays it. Take a heap snapshot (below).
- Huge JSON —
JSON.parseof a multi-hundred-MB file doubles memory (string + object graph). Stream it (JSONStream,simdjson, line-delimited JSON) or process outside Node. Validate structure of smaller samples with the JSON formatter. - webpack / CRA / Next build — giant dependency graphs, source maps, and Babel/TS transforms. Prefer: upgrade webpack 5 / Next, enable filesystem cache, split the build, build on a bigger machine/CI, or migrate CRA → Vite. Blindly setting 16 GB on a laptop hides the real cost.
- Loading entire files into strings — read streams, not
fs.readFileSyncof multi-GB inputs.
Chrome DevTools / heap snapshots (brief)
- Run with inspect:
node --inspect server.js(or--inspect-brk). - Open
chrome://inspect→ your process → Memory → Take heap snapshot. - Compare two snapshots after reproducing growth; sort by "Shallow size" / "Retained size". Look for detached DOM nodes (rare in pure Node), ever-growing Maps/Arrays, caches without bounds, and large strings.
- Clinic.js Heapprofiler /
node --heapsnapshot-signal=SIGUSR2also work on servers without a browser UI.
Docker and small VMs
# Compose / run — give the container enough RAM, then size V8 under it
docker run -m 2g -e NODE_OPTIONS=--max-old-space-size=1536 myimage
# Kubernetes: resources.limits.memory + the same NODE_OPTIONS
If builds only fail in CI: use a larger runner, reduce parallelism, or build artefacts elsewhere and copy them in. See also exit 137 when the cgroup limit wins first.
CRA / Next / webpack tips
- Create React App:
"build": "node --max-old-space-size=4096 node_modules/react-scripts/scripts/build.js"as a stopgap; long-term, ejecting or moving to Vite/Next removes a lot of peak memory. - Next.js:
next buildis memory-heavy with many routes. Disable unused source maps in production, upgrade Next, or raise the heap for the build step only. - webpack: upgrade to 5 (persistent caching), turn off
devtool: 'eval-source-map'for production, avoid loading giant JSON viarequireinto the bundle. - Old webpack 4 + Node 17+ often fails with digital envelope routines::unsupported before it gets a chance to OOM — fix that first.
Mystery binary or lockfile blob? What is this?
Related
Killed: exit code 137 (OOM) · Node OpenSSL digital envelope error · EADDRINUSE · ENOSPC no space left · EMFILE too many open files
Questions
What does JavaScript heap out of memory mean?
V8's JavaScript heap hit --max-old-space-size (or the default). The process aborts with FATAL ERROR. The machine may still have free RAM.
How do I fix heap out of memory in Node?
Try NODE_OPTIONS=--max-old-space-size=4096 for builds. If it keeps growing at runtime, find the leak or huge allocation instead of only raising the limit.
What is the difference between heap OOM and exit 137?
Heap OOM is V8 refusing to grow. Exit 137 is SIGKILL from the Linux OOM killer or a container memory limit — the whole process RSS was too big.
Is NODE_OPTIONS=--max-old-space-size safe?
Yes for a controlled build or service, if the value fits under the container/VM limit. Do not set it larger than available memory or every Node child will compete for RAM.
Why does my Next/webpack build run out of memory?
Bundlers hold large graphs, transforms and source maps in heap. Raise the limit for the build, upgrade the tool chain, enable caching, or build on a bigger machine.
How do I take a Node heap snapshot?
Start with node --inspect, open chrome://inspect, Memory tab → Take heap snapshot. Compare two snapshots to see what grew.
Published .