DEV Community

Cover image for Next.js "Server Is Running Out of Memory" Fix (2026)
Mahdi BEN RHOUMA
Mahdi BEN RHOUMA

Posted on Originally published at iloveblogs.blog

Next.js "Server Is Running Out of Memory" Fix (2026)

Shortcut: paste your failing output into the free Next.js Build Error Decoder — it recognizes the heap-out-of-memory signature (and 12 others) and links straight to the fix that applies.

The message

You are running next dev (or a Node production server), the terminal prints
this, and the server restarts:

The server is running out of memory, restarting to free up memory
Enter fullscreen mode Exit fullscreen mode

Routes reload slowly, sessions drop, and in dev your HMR stops working for a
few seconds every time it fires. This was filed as
vercel/next.js#46756 (dev
App Router + RSC accumulation) and tracked as NEXT-1353, with the production
side in vercel/next.js#49929
(NEXT-1314, high memory in deployed projects).

The message is a symptom, not a diagnosis. Next.js notices the Node heap
crossed its limit and restarts to recover. Your job is to find out why it
crossed — and there are only three possible buckets.

The three causes

  1. Dev-mode accumulation — the classic App Router + RSC hot-reload leak in Next.js 13.x. Repeated refreshes grow the heap until it restarts. This is a tooling problem, not your code.
  2. A real leak in your app or deps — an in-memory cache without eviction, event listeners never removed, a closure holding a growing dataset, a Map that never gets pruned.
  3. A legitimate heap that is too big for the limit — a route that loads a large dataset into memory at build time, a heavy getStaticProps / generateStaticParams job, or sourcemaps in production.

You diagnose by bucket. The fixes are different.

First, stop the restarts (band-aid)

Raise the V8 heap limit so you can actually work while you investigate:

# 4 GB heap (dev)
NODE_OPTIONS=--max-old-space-size=4096 npm run dev

# 8 GB heap (large production builds)
NODE_OPTIONS=--max-old-space-size=8192 npm run build
Enter fullscreen mode Exit fullscreen mode

This is not a fix. It is the room you need to take a heap snapshot instead of
getting restarted out of the inspector. Set it, then move to diagnosis.

If it only happens in dev — switch to Turbopack

The 13.x dev-mode leak in webpack's App Router pipeline is the single most
common cause of this message. If you are on Next.js 14+ and still hitting it
in dev, try Turbopack first:

next dev --turbo
Enter fullscreen mode Exit fullscreen mode

Turbopack's module graph does not accumulate on hot reload the way webpack
did, so for the classic dev leak it resolves immediately. This is the lowest-
effort, highest-hit fix in this whole article.

If you are on an older 13.x and cannot move to Turbopack, upgrade to the
latest stable — the dev accumulation was steadily reduced across 13.x and
largely resolved in 14+. Do not stay on a leaking canary.

Diagnose a real leak with a heap snapshot

If Turbopack did not help, or the leak is in production, take a heap snapshot.
Run the dev server under the inspector:

NODE_OPTIONS='--inspect' npm run dev
# or for a production Node server:
NODE_OPTIONS='--inspect' node server.js
Enter fullscreen mode Exit fullscreen mode

Open chrome://inspectinspectMemory tab → Heap snapshot.
Use the three-snapshot technique:

  1. Snapshot A — after the app warms up.
  2. Snapshot B — after running the leaking flow once.
  3. Snapshot C — after running it again.

In the comparison view (C vs B vs A), objects that grow between every snapshot
are your leak. Filter to "retained size" and look for your own domain objects
(users, rows, requests) — if you see your own types growing, the leak is in
your code. If you only see framework / node internals, the leak is upstream
(or you are holding references to your data in a structure you forgot about).

In server.js, log the heap on an interval to catch growth across requests:

// server.js (or a small instrumentation file)
setInterval(() => {
  const m = process.memoryUsage();
  console.log(
    `rss=${(m.rss / 1048576).toFixed(0)}MB ` +
    `heapUsed=${(m.heapUsed / 1048576).toFixed(0)}MB ` +
    `heapTotal=${(m.heapTotal / 1048576).toFixed(0)}MB ` +
    `external=${(m.external / 1048576).toFixed(0)}MB`,
  );
}, 10_000);
Enter fullscreen mode Exit fullscreen mode

A flat line means no leak. A steady climb that never settles means a leak.

Common app-level leaks

The usual suspects, in order of how often I have seen them:

  • Module-level Map / Set / array caches. A const cache = new Map() at module scope that you write to on every request and never evict from. Fix: bound it (an LRU), move it to a real cache (Redis / KV), or recompute.
  • Event listeners never removed. A setInterval or an event emitter you attach on every request and never removeListener / clearInterval. Each request leaks the listener and everything it closes over.
  • Closures holding large datasets. A request handler that builds a giant array inside a closure passed to a long-lived callback. The array lives as long as the callback does.
  • Sourcemaps in production. productionBrowserSourceMaps: true or a misconfigured build keeps large source content in memory. Disable for the Node server, keep them only for client error reporting.
  • Heavy generateStaticParams / build-time fetches. Pulling a large dataset into memory at build time to enumerate pages. Stream it from disk or a DB cursor instead.

Production levers

For deployed Next.js (Node server or serverless), memory is a resource you
budget for. The levers, biggest first:

  1. Move large in-memory caches out of the process. Anything that grows with traffic belongs in Redis, Upstash, or your platform's KV, not in a Map. This is the single largest production win.
  2. Stream large responses. Do not await an entire dataset into memory then JSON.stringify it. Stream it. Response with a ReadableStream, or the streaming render APIs, keep the working set small.
  3. Prune dependencies. next build shows the largest server modules. A heavy dep loaded into the server bundle sits in memory for the life of the process. Lazy-import the rare ones with await import().
  4. Disable sourcemaps on the server. They are for dev and error reporting; shipping them on the server inflates resident memory.
  5. Raise the serverless memory budget. On Vercel, serverless functions have a per-function memory ceiling (1 GB on Hobby, more on Pro — confirm on your plan). A route that genuinely needs more memory should be split out and given its own configuration rather than dragging the whole app up.
  6. Use the Edge Runtime for stateless routes. Edge functions have a smaller, fixed memory profile and no Node heap growth; move the routes that are pure request/response work over. For the gotchas of what you cannot run on Edge (no native TCP, no sync FS), the dynamic server usage / could not be rendered statically fix covers the boundary.

The dev-vs-prod split

A leak that only reproduces in next dev and never in next build / next
start
is almost always the webpack dev pipeline. Do not spend two days
hunting a leak in your code that is actually webpack's hot-reload graph —
try Turbopack for thirty seconds first.

A leak that reproduces in next start against a production build is your
code or your deps. Take the three-snapshot path above.

And the failure mode that is not a leak at all: the build passes locally, you
deploy, and it breaks in production for a different reason. That is the
build passes locally, broken in production
postmortem, not this one. They look similar from the outside and have
completely different causes.

Production checklist

  • [ ] NODE_OPTIONS=--max-old-space-size set for the build and the server.
  • [ ] No module-level unbounded caches; every cache has an eviction policy or lives outside the process.
  • [ ] No setInterval / listeners without a matching teardown.
  • [ ] Sourcemaps disabled on the Node server.
  • [ ] Large routes streamed, not buffered.
  • [ ] Heavy routes split out with their own memory budget if on serverless.
  • [ ] Stateless routes on Edge where possible.
  • [ ] An alert on process.memoryUsage().heapUsed so you see growth before the restart message does.

For the broader performance posture — what to measure, what to move to Edge,
how to keep the bundle thin — the
Next.js performance optimization guide
is the longer read. For the deploy-time failure modes that look like memory
issues but are not (middleware not running, static assets 404), see
middleware not running in Vercel production
and the deploying Next.js + Supabase to production
guide.

TL;DR

  • Restarting on memory? Raise the heap with NODE_OPTIONS=--max-old-space-size=4096 to stop the bleeding.
  • Only in dev? Switch to next dev --turbo — the classic 13.x webpack leak is the most common cause and Turbopack sidesteps it.
  • In production? Take three heap snapshots, find what grows, move caches out of the process, stream large responses, disable server sourcemaps.

Related Articles


Originally published at https://www.iloveblogs.blog

Top comments (0)