DEV Community

xabierlameiro.com
xabierlameiro.com

Posted on • Originally published at xabierlameiro.com

How to find a Next.js memory leak in production

Originally published at xabierlameiro.com.

If your self-hosted Next.js server grows until it dies with FATAL ERROR: Reached heap limit Allocation failed - JavaScript heap out of memory - or the container gets OOMKilled (exit code 137) - don't start by auditing your own code. As of July 2026 there are three documented memory leaks open in the framework itself, spanning Next.js 15.5 to 16.3.

Leak 1: the router LRU cache doesn't count its keys

#94890 - the route-resolution cache's size function adds up the value strings but never the URL key, so it can retain ~1M keys (hundreds of MB) while believing it is tiny. Signature: slow heap drift over days that tracks unique URLs served (bot crawls, long slugs), not request volume; heap-snapshot retainer traces end at LRUNode. Tip: chart your heap against distinct routes, not requests per second.

Leak 2: the RSC render tree is retained on client aborts

#94919 - streams that don't finish normally (mobile clients, crawlers, users navigating away) pin the whole element tree via the Flight request's AbortController. Much stronger on Node 22/24 than 20; the reporter measured ~2 MB per request on heavy pages. Mitigation while it stays open: trim the RSC payload of your heaviest pages.

Leak 3: middleware setTimeout ids retained by the sandbox

#95094 - the sandbox TimeoutsManager only releases a timeout id on explicit clearTimeout. A one-line workaround that works today:

const timeoutId = setTimeout(() => {
    fireAndForgetWork();
    clearTimeout(timeoutId); // releases the id from the sandbox manager
}, 0);
Enter fullscreen mode Exit fullscreen mode

Which one are you hitting?

Growth shape Correlates with Suspect
Slow, monotonic drift over days Unique URLs (bots, long slugs) Router LRU cache (#94890)
Proportional to traffic; worse on heavy pages RSC payload and client aborts RSC tree retention (#94919)
Steps the GC never recovers Requests through middleware Sandbox timeouts (#95094)
No OOM, but sporadic 504s on serverless Render cost, not memory Your render is expensive

To confirm: start with NODE_OPTIONS='--inspect' next start, take two heap snapshots separated by load, and read the retainers in Chrome DevTools' Memory tab.

On serverless the leak wears a disguise

You don't get the OOM - you get 504s. My blog's tag pages were timing out with FUNCTION_INVOCATION_TIMEOUT because of an O(N^2) post loader (a ~32-second render against a ~10-second limit); instance recycling had been hiding the waste. The fix was a module-level cache: memory spent on purpose, bounded and static per deploy.

The full diagnosis flow - which retainer maps to which issue, the exact docs commands, and my 504 postmortem with before/after numbers - is in the original post: https://xabierlameiro.com/blog/nextjs/nextjs-memory-leak-in-production

Top comments (0)