← Debugging Guides

阅读中文版本

Webpack JavaScript heap out of memory — Production Build OOM Fix

Diagnose webpack heap exhaustion by measuring build memory and stats, then raise Node's heap, disable heavy source maps, split chunks, and reduce module graph size.

intermediatedebuggingwebpackmemory

Symptoms

  • FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory
  • The build succeeds locally but fails in CI with fewer resources
  • Memory climbs steadily during minification and never drops
  • Build time and memory grow after adding a large dependency

Possible causes

  • The Node old-space limit is lower than the module graph requires
  • Production source maps generate very large strings for every module
  • A single bundle includes duplicate copies of large libraries
  • Minification and transpilation run single-threaded over too many modules

Diagnosis steps

Reproduce with an explicit heap size

NODE_OPTIONS=--max-old-space-size=4096 npx webpack --mode production

Interpretation

If the build passes at 4 GB, the default heap is the immediate limit. If it still OOMs, the graph or a plugin is generating unbounded memory.

Capture build statistics

npx webpack --mode production --profile --json > stats.json && node -e "const s=require('./stats.json'); console.log(s.modules.sort((a,b)=>b.size-a.size).slice(0,10).map(m=>[m.name,m.size]))"

Interpretation

The largest modules reveal duplicated libraries or accidentally bundled data. A module with several copies of the same package points to resolution problems.

Confirm the effective Node limit

node -e "console.log(require('v8').getHeapStatistics().heap_size_limit/1024/1024 + ' MB')"

Interpretation

Node 18 and later often default to a few gigabytes depending on platform. The build must fit below this limit plus native memory.

Isolate expensive plugins

npx webpack --mode production --no-devtool 2>&1 | tail -20

Interpretation

Passing without source maps identifies devtool as the multiplier. Compare build time and peak RSS with devtool set to false.

Root cause

Webpack holds the module graph, source text, ASTs, and generated chunks in memory at once. Source maps and minification create additional large strings, so peak usage can be several times the size of the source. When V8 cannot allocate within the old-space limit it aborts the process; CI containers with low memory hit the same wall even if the local machine succeeds.

Fix

Set devtool to false or an external source map type in production, raise the Node heap to a measured value, and split the build into smaller entries or use persistent caching. Remove duplicate dependencies, use dynamic imports for rarely used routes, and upgrade webpack and loaders. In CI, size the container above the measured peak rather than only raising NODE_OPTIONS.

Risk note

Raising the heap can mask a genuine leak and may cause the OS OOM killer to terminate the build instead. Disabling source maps removes production debuggability. Increasing parallelism multiplies memory rather than reducing it.

Verify the fix

Rebuild from a clean cache with the new settings and confirm it completes below the configured heap, then record peak memory in CI. Compare bundle sizes and verify the application still loads and source maps, if enabled, resolve correctly.

The default heap is a hard ceiling

FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory

Webpack keeps the module graph, parsed sources, and generated output in memory. Production minification and source maps add large intermediate strings, so a build that uses 1.5 GB of source can peak far above the default old-space limit.

Measure before raising the limit

node -e "console.log(require('v8').getHeapStatistics().heap_size_limit/1024/1024 + ' MB')"
NODE_OPTIONS=--max-old-space-size=4096 npx webpack --mode production
npx webpack --mode production --profile --json > stats.json

If 4 GB completes the build, the limit was the trigger. If it still fails, inspect stats.json for duplicated packages and accidental data imports. A bundle containing three copies of a UI library is a resolution problem, not a heap problem.

Reduce peak memory at the source

// webpack.config.js
module.exports = {
  mode: 'production',
  devtool: false,
  cache: { type: 'filesystem' },
  optimization: {
    splitChunks: { chunks: 'all' },
  },
};

Disable or externalize source maps, split large entries, lazy-load rarely used routes, and remove unused polyfills. Raising --max-old-space-size without these changes just moves the failure to the next dependency.

Verify

Rebuild from a clean cache and confirm completion with the chosen heap. Record peak RSS in CI so a future regression is visible before it becomes an outage. Check the emitted bundle still boots, and if source maps are external, confirm they resolve to the original sources rather than only existing as files.