JavaScript heap out of memory during next build
THE SHORT ANSWER
next build fails with "JavaScript heap out of memory" when the Node.js process running the build fills V8's old-space heap. The heap has a size limit of its own, separate from how much RAM the machine has. The quick fix is to raise that limit for the build only, with --max-old-space-size set below the memory the build machine actually has. The lasting fix is to find which build step uses the memory (bundling, TypeScript or static page generation) and turn down that step: source maps, the type check, or webpack's cache.
What does JavaScript heap out of memory mean?
Node.js keeps JavaScript objects in V8's heap, and the long-lived part of it, old space, has a maximum size. As memory use nears that limit, V8 spends more and more time collecting garbage. When a collection can't free enough, V8 stops the process and prints one of these:
<--- Last few GCs ---> ... FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory # or, from older Node versions FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed - JavaScript heap out of memory
The limit is V8's, not the machine's. A laptop with 16 GB of RAM can still hit it, because Node picks its default heap size from the memory it detects and doesn't grow past it. A build that worked last month fails today because the app grew: more pages, more dependencies, a bigger type graph.
Is it the heap or the whole machine?
Two different failures look alike in a build log. Tell them apart before you fix anything:
| What the log shows | What ran out | What helps |
|---|---|---|
FATAL ERROR: ... JavaScript heap out of memory | Node's heap limit. The machine may still have free memory | A bigger heap limit, or less memory used by the build |
Killed, exit code 137, or the build stops with no error at all | The container or machine. The kernel's OOM killer sent SIGKILL | Less memory used by the build, or a machine with more. A bigger heap limit makes this worse |
The second case is why the usual advice, --max-old-space-size=8192, sometimes “fixes” the heap error and replaces it with exit code 137. A heap limit above the memory the build really has lets V8 keep allocating until the kernel kills it.
How do I raise the heap limit for next build?
Pass --max-old-space-size, in MiB, to the Node process that runs the build. Put it in the build script, so it applies to the build and not to the server you run afterwards:
// package.json
"scripts": {
"build": "NODE_OPTIONS=--max-old-space-size=3072 next build",
"start": "next start"
}
// The same without an environment variable (works in any shell)
"build": "node --max-old-space-size=3072 node_modules/next/dist/bin/next build"The first form needs a Unix shell; on Windows use cross-env or the second form. Pick a number below the memory the build machine has, leaving room for everything outside the heap. Node's own docs suggest 1536 on a machine with 2 GiB. Rough guide:
| Memory on the build machine | --max-old-space-size |
|---|---|
| 2 GiB | 1536 |
| 4 GiB | 3072 |
| 8 GiB | 6144 |
Setting NODE_OPTIONS as an environment variable on your host also works, but it reaches every Node process, including the production server. A 3 GB heap limit on a server container with 512 MB of memory turns a slow garbage collection into an OOM kill.
Which build step runs out of memory?
Read the last line next build printed before the crash. Each step has its own fix, all from the Next.js memory usage guide:
| Last step in the log | What uses the memory | Setting to try |
|---|---|---|
Creating an optimized production build | Bundling, and source maps for the browser and server | productionBrowserSourceMaps: false, experimental.serverSourceMaps: false; webpack builds: see below |
Running TypeScript | The type check of the whole project | Run tsc --noEmit in CI and set typescript.ignoreBuildErrors: true |
Generating static pages | Prerendering every static page; with cacheComponents, source maps for the prerender | enablePrerenderSourceMaps: false, or fewer pages prerendered at build time |
Skipping the type check is safe only if something else runs it. The Next.js docs warn that it “may produce faulty deploys due to type errors”, so add tsc --noEmit to CI or a pre-push hook before you turn it off.
// next.config.ts
import type { NextConfig } from 'next'
const config: NextConfig = {
productionBrowserSourceMaps: false,
experimental: {
serverSourceMaps: false,
},
typescript: {
// only with tsc --noEmit running in CI
ignoreBuildErrors: true,
},
}
export default configBuilding with webpack?
Next.js 16 builds with Turbopack by default. If your build runs next build --webpack, or you are on Next.js 15 or older, three webpack settings cut memory use:
experimental.webpackMemoryOptimizations: true(Next.js 15 and later) lowers peak memory at the cost of slightly slower compiles. Next.js calls it low-risk.experimental.webpackBuildWorker: trueruns compilation in a separate worker. It is on by default since 14.1 unless you have a customwebpackconfig, so check this one if you do.- Webpack's persistent cache holds modules in memory. The Next.js guide shows how to switch it to the memory-only cache in production builds:
webpack: (config, { dev }) => {
if (config.cache && !dev) {
config.cache = Object.freeze({ type: 'memory' })
}
return config
},How do I see what is using the memory?
If the limit keeps climbing, measure before raising it again:
# Print heap use and GC stats through the build; snapshots near the limit next build --experimental-debug-memory-usage # Write a .heapprofile to load in Chrome DevTools > Memory node --heap-prof node_modules/next/dist/bin/next build
The debug mode doesn't work with the webpack build worker. In the profile, look for one package that owns a large share of allocations: an icon set imported whole, a large JSON file, a charting library pulled into every page. The bundle analyzer shows the same thing from the output side, and removing or lazy-loading one large dependency often does more than any setting above.
What happens on EthioDeploy?
EthioDeploy builds your app in a separate build container, so a build that runs out of memory fails the deploy and the previous version keeps serving traffic. The build log shows the same FATAL ERROR line or an exit code 137 as anywhere else, and the fixes above apply as written.
Put --max-old-space-size in the build script, not in a NODE_OPTIONS environment variable. Project environment variables reach both the build and the running app, and on the Free plan the app container caps the Node heap below its 256 MB memory limit so Node collects garbage before the kernel kills it. A --max-old-space-size of your own in NODE_OPTIONS replaces that cap, so a value sized for the build would also apply to the 256 MB server. The deploy log says when the cap is applied.
If your app needs more memory at runtime too, see pricing for plan limits, and Deploy Next.js for the full setup.
Questions
Raise the Node.js heap limit for the build only, for example "build": "node --max-old-space-size=3072 node_modules/next/dist/bin/next build" in package.json, with the number below the memory the build machine has. Then cut what the build uses: turn off source maps, run the type check in CI instead of in next build, and on webpack builds turn on experimental.webpackMemoryOptimizations.
Below the memory the build machine or container actually has, leaving room for memory outside the heap. Node's docs suggest 1536 on a machine with 2 GiB. A value above the real memory lets V8 grow until the kernel kills the build with exit code 137.
Exit code 137 means the kernel killed the process because the container or machine ran out of memory. That is not the heap limit. Raising --max-old-space-size won't help and can cause it. Use less memory in the build or build on a machine with more.
Next.js 16 builds with Turbopack by default. Turbopack does most of its work in native code rather than in the Node.js heap, but the TypeScript check and static page generation still run in Node, so heap errors can still happen there.
Only if the type check runs somewhere else, such as tsc --noEmit in CI. Otherwise type errors reach production. The Next.js docs recommend promoting a build only after static analysis has passed.
Sources
Checked on November 3, 2026:
Join the EthioDeploy Telegram
Release notes as they ship, plus deploy help from the team and other developers.