How to Fix the JavaScript Heap Out of Memory Error
Fix Node.js and browser JavaScript heap errors: identify the failing runtime, set a safe limit, and find memory leaks with heap snapshots.
Quick fix: first determine whether Node.js, a build tool, or a browser tab failed. For a Node.js process, raise V8’s old-space limit only when the host has enough memory, for example node --max-old-space-size=1536 index.js. If memory keeps growing or the error returns, use heap snapshots to find retained objects instead of increasing the limit indefinitely.
The message FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory means that V8 could not satisfy an allocation near its configured heap limit. The limit is a capacity setting; it does not explain which objects consumed memory or prove that a leak exists. Node documents --max-old-space-size=SIZE in MiB and notes that garbage collection becomes more frequent as usage approaches the limit (Node.js CLI documentation).
1. Identify the runtime that failed
| Where you saw the error | What it means | Use this first |
|---|---|---|
node, npm script, test runner, or server log |
Node/V8 process reached its heap limit | Check the host budget, then set --max-old-space-size and profile if needed |
| webpack or another frontend build | The build’s Node process ran out of heap | Pass the setting through NODE_OPTIONS |
| Chrome or another browser tab | The page’s JavaScript objects or DOM consumed memory | Use DevTools Memory heap snapshots; a Node flag cannot repair that tab |
Do not copy a Node command-line flag into browser debugging. A browser page has its own process and memory tools. Chrome’s Memory panel can record and compare heap snapshots (Chrome heap snapshot documentation).
2. Check the real memory budget
The V8 heap is only one part of total usage. Leave memory for the operating system, native allocations, worker processes, file caches, and the build itself. Node’s documentation gives 1536 MiB on a 2 GiB machine as an example that leaves room for other uses and helps avoid swapping. It is an example, not a universal value.
Check available memory before raising the cap:
# Linux
free -h
cat /proc/meminfo | head
# macOS
vm_stat
sysctl hw.memsize
# Container: inspect the limit visible to the container
cat /sys/fs/cgroup/memory.max 2>/dev/null || true
cat /sys/fs/cgroup/memory/memory.limit_in_bytes 2>/dev/null || true
Choose a value below the memory available to the process’s environment. A value larger than the machine or container can provide may cause swapping or an out-of-memory termination at the operating-system level.
3. Raise the Node.js heap limit safely
Run a Node program directly
node --max-old-space-size=1536 index.js
The number is MiB. Adjust it to the host’s budget and workload; do not blindly use 4096 or another large number.
Set it with NODE_OPTIONS
# macOS/Linux
NODE_OPTIONS="--max-old-space-size=4096" npm run build
# Windows PowerShell
$env:NODE_OPTIONS="--max-old-space-size=4096"
npm run build
# Windows cmd.exe
set NODE_OPTIONS=--max-old-space-size=4096 && npm run build
Webpack documents the same pattern for its CLI (webpack CLI documentation). Use a value that fits the CI runner or container, not necessarily the documentation example.
Make the setting part of an npm script
{
"scripts": {
"build": "webpack",
"build:large": "node --max-old-space-size=3072 ./node_modules/webpack/bin/webpack.js"
}
}
Keeping the larger setting in a separate script makes the exceptional workload visible and avoids changing every command.
4. Decide whether the failure is a peak or a leak
- Bounded peak: the process reaches a stable high-water mark during a large compilation, data transform, or bundle, then exits. A supported increase may be appropriate.
- Sustained growth: each request, test batch, watch rebuild, or browser interaction leaves more objects retained. Find the retaining references; a larger limit only delays failure.
- Host pressure: the process is killed without the V8 message, or the container reports an OOM kill. Reduce concurrency or workload and allocate more host memory; the V8 flag alone cannot solve this.
5. Diagnose a Node.js process with heap snapshots
Node supports taking heap snapshots near the heap limit, and its diagnostics guide explains how to inspect snapshots in Chrome DevTools (Node.js heap snapshot guide). Snapshot creation pauses the main thread and can require substantial additional memory, so use it carefully on production processes.
- Reproduce the workload with a repeatable input.
- Capture a snapshot before the workload and another after the same workload has completed.
- Open both snapshots in Chrome DevTools and compare retained object counts and sizes.
- Inspect retaining paths. Look for module-level arrays, unbounded caches, event listeners, timers, queues, and request data kept after completion.
- Change one ownership or cleanup path, then repeat the comparison.
Capture from a diagnostic process when possible. A practical pattern is to expose a protected diagnostic endpoint that writes a snapshot, or start Node with the diagnostic facilities appropriate to your deployment. Restrict access because snapshots can contain secrets and request data.
6. Diagnose a browser tab with Chrome DevTools
- Open DevTools, select Memory, and take a heap snapshot.
- Perform the action that appears to leak, such as navigating, opening a modal, or running a long-lived editor workflow.
- Take another snapshot and compare it with the first.
- Sort by retained size and inspect retaining paths for objects that should have been released.
- Check detached DOM nodes. Chrome explains that a node removed from the document can remain alive while JavaScript still references it (Chrome memory-problem guide).
- Remove stale references, unregister event listeners, clear timers, and bound caches only when the reference is no longer needed.
7. Common fixes by workload
| Symptom | Likely cause | Action |
|---|---|---|
| Production build fails near the same point every time | Build peak exceeds the current old-space cap | Raise the cap within the runner budget; reduce parallelism if the host is constrained |
| Watch mode grows after every rebuild | Plugins, module caches, listeners, or compiler state retained | Profile two or more rebuild cycles and inspect retained objects |
| Server grows with every request | Request data, cache entries, timers, or listeners retained | Take snapshots at equal request counts and compare retaining paths |
| Browser page grows after opening and closing UI | Detached DOM nodes or listener closures | Use DevTools snapshots and remove stale references |
| Container is killed after increasing the flag | Container memory limit is lower than the chosen heap plus overhead | Lower the heap, reduce concurrency, or increase the container limit |
8. Troubleshooting errors
“Invalid value for –max-old-space-size”
Use a positive integer in MiB, such as 1536. Put the option before the script path: node --max-old-space-size=1536 index.js.
The command works locally but fails in CI
CI often has less memory or a cgroup limit. Inspect the runner/container budget and set NODE_OPTIONS in the CI job. Also reduce worker count and build concurrency.
The error returns after raising the limit
Measure growth across repeated, equal workloads. If retained memory rises, capture and compare heap snapshots. Increasing the limit is not a leak fix.
There is no V8 message; the process just disappears
Check container, host, and platform logs for an operating-system OOM kill. V8 heap settings do not increase the total memory available to the process.
Heap snapshots stop or crash the service
Snapshot creation pauses execution and may need significant memory. Reproduce in staging or a diagnostic replica, and protect snapshot files because they may contain sensitive data.
9. Performance, reliability, and cost considerations
- Performance: a larger heap can reduce garbage-collection pressure for a legitimate large workload, but memory pressure and swapping can make the system slower.
- Reliability: reserve memory for native allocations, workers, the runtime, and the operating system. Set container limits and process concurrency together.
- Observability: record process RSS, heap-used, heap-total, event-loop delay, restart count, and workload size. Heap usage alone does not show native memory.
- Cost: increasing CI runner or container memory can increase infrastructure cost. First remove unbounded retention and unnecessary concurrency; then size the host for the remaining peak.
10. Or skip the browser setup
If the JavaScript error happened while building your own screenshot workflow, ScreenshotNeo provides a hosted capture request. It handles the browser process for you:
# cURL
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
# Python
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
// Node.js
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
See the ScreenshotNeo API documentation for the request options. Cookie and consent banners, newsletter popups, and chat widgets are removed before the shot. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers identify the page verdict and billing status. An MCP server lets Claude, Cursor, and other MCP clients take screenshots. The Free plan includes 1,000 screenshots each month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.
FAQ
Does --max-old-space-size fix a memory leak?
No. It raises V8 old-space capacity. Use snapshots and retaining paths to investigate continued growth.
What unit does the flag use?
MiB. For example, 1536 means 1536 MiB.
Should I always set the largest possible value?
No. Leave room for the operating system, native memory, workers, and other processes.
Can this flag fix a browser tab?
No. Profile the page with Chrome DevTools Memory snapshots.
Why can a detached DOM node cause a leak?
A removed node can remain reachable when JavaScript still holds a reference to it. Inspect its retaining path before removing the reference.
How many snapshots should I compare?
At least two snapshots taken at equivalent points in a repeatable workload; more snapshots help show whether growth is linear or a one-time peak.


