Quick answer: Set NODE_OPTIONS=--max-old-space-size=4096 before running npm build, or add it to the build script. That raises Node's heap ceiling from its default (often 2GB) to 4GB. If it still dies, you've got a memory leak or a hardware wall.
I know this error is infuriating. You're mid-deploy, the terminal spits out a wall of garbage ending in FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory, and npm exits with a code your CI pipeline treats like a heart attack. The specific trigger for ERR_WORKER_OUT_OF_MEMORY is usually a build tool like Webpack or Vite spawning worker threads that each try to allocate more memory than Node's default heap allows.
Here's the thing most people miss: Node's heap limit isn't a hard OS limit. It's a V8 setting. On a machine with 32GB of RAM, Node still acts like it's on a 2GB machine until you tell it otherwise. That default was chosen years ago when 2GB was a lot. Now every project ships with a 500MB node_modules and a source map config that eats memory like popcorn. The error isn't your code failing—it's Node being polite about resource usage in the worst possible way.
Fix 1: Raise the heap limit (the one you'll actually use)
This works 80% of the time. Run your build with a bigger ceiling.
On macOS or Linux:
NODE_OPTIONS=--max-old-space-size=4096 npm run build
On Windows (cmd):
set NODE_OPTIONS=--max-old-space-size=4096 && npm run build
On Windows (PowerShell):
$env:NODE_OPTIONS="--max-old-space-size=4096"; npm run build
4096 means 4GB. If your machine has 16GB or more, bump it to 8192 (8GB). Don't go higher than half your physical RAM—Node will happily try to use it and then your OS starts swapping, which is slower than just allocating more upfront.
The permanent fix is to put this in your package.json build script so you don't have to remember flags every time:
"scripts": {
"build": "cross-env NODE_OPTIONS=--max-old-space-size=4096 webpack --mode production"
}
Use cross-env if your team runs a mix of Windows and Unix machines. Yes, it's an extra dependency. No, you don't want to maintain two scripts.
Fix 2: Find the actual memory hog
If 8GB still isn't enough, you're not fixing the error—you're postponing it. Something in your build is retaining memory it shouldn't. The usual suspects:
- Source maps. Generating full source maps for a 2MB bundle can triple memory use. Try
devtool: 'source-map'→'cheap-source-map'in production. You lose some debugging fidelity. You gain a build that finishes. - Babel transforming node_modules. If your
babel-loaderconfig doesn't haveexclude: /node_modules/, it's compiling every dependency from scratch. Add that exclusion. - Circular imports. Rare, but a circular dependency chain across 50+ modules can cause Webpack's module graph to balloon. Run
npx madge --circular src/to check. - Worker threads piling up.
ERR_WORKER_OUT_OF_MEMORYspecifically means a worker process hit its limit. Tools like Terser orthread-loaderspin up one worker per CPU core by default. On a 16-core CI runner, that's 16 heaps each trying to hold data. Cap it:new TerserPlugin({ parallel: 4 }).
Fix 3: Split the build
If you're building a monorepo or a huge app, one giant build process is the problem. Break it up:
npm run build:client
npm run build:server
npm run build:workers
Each gets its own Node process with its own fresh heap. It's slower overall, but it finishes. I've seen teams drop build failures from daily to zero this way.
Alternative fixes if none of that works
Check your Node version
Node 16 and earlier had worse garbage collection for large heaps. If you're still on Node 16, upgrade to Node 20 LTS or 22. Run node --version. This alone has fixed it for people I've helped.
Disable the worker entirely
Some build tools let you opt out of worker threads. For Webpack, parallel: false in the optimization config. You lose build speed, but the memory ceiling problem goes away because everything stays in the main process.
Swap the build tool
If you're on an old Webpack 4 setup and can migrate to Vite or esbuild, do it. Vite's production build uses Rollup, which is dramatically more memory-efficient. This is a big change though—don't do it the day before a release.
Throw hardware at it
Your CI runner probably has 4GB of RAM. That's not enough for a modern JS build anymore. Bump the runner to 8GB or 16GB. This isn't a code fix, but sometimes it's the honest one.
Prevention: stop it from coming back
Put the NODE_OPTIONS setting in a .env file at your project root and load it in CI. Set a memory budget in your build tooling—Webpack has performance.maxAssetSize and maxEntrypointSize warnings that catch bloat before it becomes a crash. And check your bundle size on every PR with a tool like bundlewatch or size-limit.
The real fix is treating build memory like any other resource: measure it, cap it, and alert when it grows. The heap limit flag gets you unblocked today. The prevention work keeps you from Googling this error again in six months.