FATAL ERROR: JavaScript heap out of memory

Quick answer

Node exhausted V8's heap. For a genuinely large job, raise the limit with --max-old-space-size=4096 (or NODE_OPTIONS for build tools). If memory steadily climbs in a long-running process, it is a leak — raising the limit only delays the crash.

The exact error string

<--- Last few GCs --->
[12345:0x...]    41523 ms: Mark-sweep 2047.8 (2068.1) -> 2047.0 (2068.6) MB

<--- JS stacktrace --->

FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory
 1: 0x... node::Abort()
 2: 0x... v8::internal::Heap::CollectGarbage(...)
 ...

The giveaway is the Last few GCs block: V8 ran garbage collection over and over, freed almost nothing (the numbers barely drop), and finally aborted.

Ineffective mark-compacts near heap limit

Just before the fatal error, V8 often prints Ineffective mark-compacts near heap limit Allocation failed. This is the warning sign, not a separate problem: a mark-compact is V8's most thorough garbage-collection pass, and “ineffective” means even that full sweep reclaimed almost no memory. V8 is spending more and more time in GC for less and less gain — the application is right up against the heap ceiling. If you catch this message in logs while the process is still alive, it is the moment to act before the crash: the fix is the same as for the fatal error below.

What the heap is

The heap is the region of memory where V8 stores your JavaScript objects, strings, arrays, and closures. V8 enforces a maximum heap size; when an allocation would exceed it and garbage collection cannot free enough room, V8 aborts the whole process rather than limp along in an unstable state. The crash is fatal and uncatchable — you cannot wrap it in a try/catch.

The default limit depends on your Node version and the machine's RAM. Older Node capped old-space around 2 GB on 64-bit systems; newer versions scale the default to physical memory — which is why a build can pass on a 16 GB laptop and fail in a 512 MB CI container.

Most common causes

The error shows up in two broad situations — heavy one-time work, and slow leaks. These are the cases developers hit most often:

First, decide: large job or leak?

This single question determines the correct fix, and getting it wrong wastes hours:

Fix: raise the heap limit (for large jobs)

Give V8 a bigger old-space with --max-old-space-size, in megabytes. Run the script directly with the flag:

# 4 GB heap
node --max-old-space-size=4096 app.js

Most of the time the memory-hungry process is a build tool you do not invoke node for directly — webpack, tsc, Jest, Next.js, Vite. Pass the flag through the NODE_OPTIONS environment variable so every child Node process inherits it:

# 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

# Make it durable in package.json:
{
  "scripts": {
    "build": "cross-env NODE_OPTIONS=--max-old-space-size=4096 webpack"
  }
}

The cross-env in that package.json example sets the variable the same way on Windows, macOS, and Linux — install it first with npm install -D cross-env, or drop it if you only ever run the script on a single platform.

Only set the limit as high as the machine actually has RAM. Asking for 8192 on a 4 GB container will not help — the OS will kill the process (an OOM kill / exit code 137) before V8 reaches its limit.

One subtlety: --max-old-space-size caps only V8's old-space, not the entire process. Total resident memory (RSS) also includes the young generation, native buffers, and code, so a process you capped at 4096 can still show 5 GB+ in top or Task Manager. Leave headroom above the flag value when sizing a container.

JavaScript heap out of memory in Next.js, Vite, and Webpack

Build tools are the most common place this error appears, because bundling holds the whole module graph in memory. The flag goes in the same place for each — via NODE_OPTIONS on the build script — but the script names differ:

// package.json (cross-env installed: npm install -D cross-env)
{
  "scripts": {
    "build:next":    "cross-env NODE_OPTIONS=--max-old-space-size=4096 next build",
    "build:vite":    "cross-env NODE_OPTIONS=--max-old-space-size=4096 vite build",
    "build:webpack": "cross-env NODE_OPTIONS=--max-old-space-size=4096 webpack",
    "build:tsc":     "cross-env NODE_OPTIONS=--max-old-space-size=4096 tsc -p ."
  }
}

If the build still dies after raising the limit, the bundle itself is the problem, not the ceiling: look for huge dependencies pulled in whole, source maps on a giant codebase, or a Next.js SSG run generating thousands of pages at once. Splitting the build, disabling source maps in CI, or upgrading the bundler often does more than another gigabyte of heap.

Fix: it fails in Docker or CI

Containers and CI runners usually have far less memory than your laptop, and Node may size its default heap from the host instead of the container's cgroup limit. Two things to set:

If you see exit code 137 rather than the V8 fatal error, the OOM killer terminated the process — the container itself is too small, regardless of the V8 flag.

Fix: find and stop the leak

If memory climbs steadily, capture a heap snapshot and compare snapshots over time to find what keeps growing. Node can dump one automatically just before the crash:

# Write a .heapsnapshot just before V8 hits the limit
node --heapsnapshot-near-heap-limit=1 app.js

# Or inspect a live process in Chrome DevTools
node --inspect app.js   # then open chrome://inspect → Memory → take snapshots

Open the snapshot in Chrome DevTools → Memory and look for object types whose count or retained size grows between snapshots. The usual leak sources:

Reduce memory pressure

Whether it is a leak or a genuinely large workload, processing data more frugally often removes the need for a bigger heap entirely. Stream large files instead of reading them fully into memory; paginate database queries instead of loading every row; process records in batches; and release references when you are done so GC can reclaim them. A streaming JSON parser, for instance, keeps memory flat regardless of file size.

Debugging checklist

Frequently Asked Questions

What does JavaScript heap out of memory mean?

It means the Node process tried to allocate more memory than V8's heap limit allows, so V8 aborted the process with a fatal error. The heap is where your JavaScript objects live. When garbage collection can no longer free enough space to satisfy an allocation, V8 gives up rather than continuing in an unstable state.

How do I increase the Node memory limit?

Raise V8's old-space limit with the --max-old-space-size flag, in megabytes. For example, node --max-old-space-size=4096 app.js gives a 4 GB heap. To apply it to a tool you do not call directly (webpack, tsc, jest), set it via the environment: NODE_OPTIONS=--max-old-space-size=4096. Only raise it as high as the machine's available RAM allows.

Should I just increase the memory limit or is it a leak?

If the job is legitimately large — a big production build, processing a huge file — raising --max-old-space-size is the right fix. If memory climbs steadily over time in a long-running server until it crashes, that is a memory leak and raising the limit only delays the crash. Watch the memory curve: a one-time spike means raise the limit; a steady climb means find the leak.

What is the default heap size in Node.js?

It depends on the Node version and available system memory. Older Node versions capped the old-space heap around 2 GB on 64-bit systems by default. Newer versions size the default heap based on the machine's physical RAM, so on a small container the default can be much lower than you expect — which is why builds that pass locally fail in CI or Docker.

Why does it happen in Docker or CI but not locally?

Containers and CI runners often have far less memory than your laptop, and Node may size its default heap from the host rather than the container's cgroup limit. The same build that has 16 GB locally might get 512 MB in CI. Set NODE_OPTIONS=--max-old-space-size to a value that fits the container, and make sure the container itself is given enough memory.

How do I find what is using the memory?

Take a heap snapshot. Run node with --heapsnapshot-near-heap-limit=1 to capture a snapshot just before the crash, or use the Chrome DevTools Memory tab against a --inspect session. Compare snapshots over time to find objects that keep growing — large arrays, caches without eviction, or event listeners that are never removed are the usual culprits.

Working with huge JSON files?

Validate and inspect JSON in your browser before loading it into Node.js — nothing is uploaded to a server.

JSON Formatter JSON Validator All Error References
About the author

Pasindu Ishan is a software developer based in Sri Lanka. He builds privacy-first developer tools at JSON Dev Tools.