Common causes
- Large frontend builds (webpack, Vite, Next.js, Angular, TypeScript type-checking) exceeding the default heap on smaller machines
- Reading an entire large file, query result or API response into memory instead of streaming it
- A memory leak: caches, arrays or event listeners that grow forever in a long-running server
- Building on a small VPS or CI runner with little RAM, where the OS kills the process or the heap limit is low
- Recursive or runaway code creating objects in a loop
How to fix it
- Raise the heap limit for the command. Set NODE_OPTIONS=--max-old-space-size=4096 (in MB) before the command, e.g. NODE_OPTIONS=--max-old-space-size=4096 npm run build. Keep it below the machine's free RAM.
- Make it permanent for builds. Add the flag to the script in package.json or to your CI environment variables so every build gets the same limit.
- Check available RAM. Run free -m. If the server has 1-2 GB, add swap or build on your machine or in CI and deploy only the built files.
- Stream instead of loading everything. Use fs.createReadStream, streaming JSON/CSV parsers and database cursors for big data instead of readFileSync or one huge query.
- Find a leak in long-running apps. If memory climbs steadily over hours, start Node with --inspect, take heap snapshots in Chrome DevTools a few minutes apart and compare what keeps growing.
- Upgrade Node and tooling. Newer Node LTS versions and bundler releases use memory more efficiently; source maps and type-checking in the same process are common heavy hitters you can split out.
package.json
{
"scripts": {
"build": "node --max-old-space-size=4096 node_modules/vite/bin/vite.js build",
"build:ci": "cross-env NODE_OPTIONS=--max-old-space-size=4096 next build"
}
} How to stop it happening again
- Build assets in CI or locally rather than on small production servers
- Stream large files and paginate database queries
- Monitor process memory (pm2 monit, metrics) to catch leaks before they crash