#nextjs server production js heap out of memory

1 messages · Page 1 of 1 (latest)

turbid siloBOT
#

Hi @young hazel. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave:

#

You're hitting a well-documented memory leak in Next.js 16.1.6 with output: standalone. The stack trace pointing to next-server (v16.1.6) matches exactly what others have reported.

Root Cause

Three bugs in Next.js 16.1.6 interact to cause unbounded ArrayBuffer growth:

  1. clone-response.js: Only one of two tee'd stream clones is registered with FinalizationRegistry, so the other's underlying buffers are never auto-cancelled (fixed in PR #88577).
  2. cache-handlers/default.js: Empty ReadableStream bodies get size = 0 in the LRU cache, meaning they never trigger eviction and the cache grows unboundedly (fixed in PR #89040).
  3. Response pipeline retention: WriteWrap objects created during response piping are pinned to GC roots and never freed without the FinalizationRegistry fix (partially addressed in PR #90771).

The result is arrayBuffers growing linearly at ~5 MB/s, never reclaimed even with manual GC. [ArrayBuffer retention issue]

The Fix

Upgrade to next@16.2.2 or later (or next@canary). All three fixes (#88577, #89040, #90771) are present in 16.2.0-canary.79 and later stable releases. Users who upgraded confirmed that arrayBuffers return to baseline after load stops. [canary fix confirmed]

Workarounds (if you can't upgrade immediately)

#
  • Restart containers periodically — some users restart every 8–16 hours to avoid OOM kills. [dev server leak]
  • Set a memory limit on the container so Node.js is forced to GC more aggressively rather than growing indefinitely.
  • Replace fetch with node-fetch — one user reported this drastically reduced memory growth by opting out of Next.js's fetch caching layer, though it loses Next.js caching features. [node-fetch workaround]
  • Use --expose-gc and call global.gc() periodically, though this was reported as ineffective for the arrayBuffers leak specifically.

The most reliable fix is upgrading. The leak is a known regression introduced after 15.4.0 and confirmed fixed in canary builds. [memory regression]

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@young hazel Kapa.ai is still learning and improving, please let me know how I did by reacting below.

turbid siloBOT
#

Let me check the knowledge base for you again. Give me one moment! :books:

#

This error — "The router state header was sent but could not be parsed" — is a known issue that can occur when proxy.ts is involved in App Router RSC (_rsc) navigations.

What's happening

When your proxy.ts has a broad matcher (covering most or all routes), it runs for RSC navigation requests (e.g. /some-page?_rsc=...). Even if your proxy doesn't intentionally touch internal RSC headers like next-router-state-tree, the proxy execution path can cause parseAndValidateFlightRouterState() to fail and throw this error. [RSC navigation failures]

There's also a separate but related scenario: if you're running two Next.js apps of different major versions behind the same domain, the Next-Router-State-Tree header schema changed between versions (e.g. Next 14 vs Next 16), causing the newer server to fail parsing the older client's header. [cross-version RSC failure]

Things to check

1. Narrow your proxy matcher

The most effective mitigation reported is narrowing the config.matcher in proxy.ts so it doesn't run for normal App Router routes. One user confirmed that switching from a broad matcher to a narrow one stopped this class of errors entirely: [RSC navigation failures]

export const config = {
  matcher: [
    // Only match specific paths you actually need to proxy
    '/specific-path/:path*',
  ],
};

2. Avoid modifying RSC-related headers

#

Next.js strips internal Flight headers (rsc, next-router-state-tree, next-router-prefetch) from request.headers inside Proxy to prevent accidental mishandling. However, if you're setting response headers broadly, be careful not to interfere with these. [setting headers]

3. If you have non-ASCII characters in headers

If your proxy is forwarding headers that contain non-ASCII characters (e.g. from Cloudflare's geo headers like Île-de-France), this can cause malformed header errors. A workaround is to sanitize headers before returning: [encoding issue]

function sanitizeHeaders(response: NextResponse): NextResponse {
  for (const [key, value] of response.headers.entries()) {
    if (!/^[\x00-\x7F]*$/.test(value)) {
      response.headers.delete(key);
    }
  }
  return response;
}

4. Cross-version multi-zone setup

If you're running multiple Next.js apps behind the same domain at different versions, the only workarounds are stripping RSC headers at the CDN/proxy layer (outside Next.js), using plain <a> tags instead of <Link> for cross-app navigation, or upgrading all apps to the same version. [cross-version RSC failure]


The most actionable first step given your setup is to narrow your proxy.ts matcher to only the routes that actually need proxy logic.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@young hazel Kapa.ai is still learning and improving, please let me know how I did by reacting below.

#

Let me check the knowledge base for you again. Give me one moment! :books:

#

I don't see any attached file in your message — it looks like the proxy.ts file didn't come through successfully.

Could you paste the contents of your proxy.ts directly into the chat? Once I can see the code, I can give you specific advice based on what your proxy is doing with headers and matchers.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@young hazel Kapa.ai is still learning and improving, please let me know how I did by reacting below.

#

Let me check the knowledge base for you again. Give me one moment! :books:

#

Looking at your proxy.ts, here are the key observations and recommendations based on known issues:

What your proxy does

  1. Rewrites paths with a locale prefix (e.g. /en/about/about with locale headers)
  2. Redirects to add/remove locale prefixes
  3. Passes through ignored/slug paths with custom request headers

Likely cause of the RSC error

Your proxy has a broad matcher (you haven't shown config, but given it handles all paths including locale detection, it almost certainly runs on every route). This is the primary trigger for the "router state header could not be parsed" error — even a proxy that doesn't intentionally touch RSC headers can cause this when it runs on RSC navigation requests. [RSC navigation failures]

Recommended fixes

1. Narrow your matcher — exclude static assets and Next.js internals:

export const config = {
  matcher: [
    '/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
  ],
};

2. Be careful with NextResponse.rewrite() — you're already using it correctly (Next.js automatically propagates RSC headers on rewrites), so that part is fine. [setting headers]

3. Check buildRequestHeaders and attachLocaleCookie — if either of these sets headers with non-ASCII values (e.g. locale display names like "Île-de-France"), that can corrupt RSC headers. Sanitize any header values you set:

function sanitizeHeaderValue(value: string): string {
  return value.replace(/[^\x00-\x7F]/g, '');
}

4. If you need the full config matcher reviewed, please share it — the matcher scope is the most likely culprit here.

#

To confirm: could you share your config.matcher and the buildRequestHeaders function? Those are the two most likely sources of the issue.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@young hazel Kapa.ai is still learning and improving, please let me know how I did by reacting below.

#

Let me check the knowledge base for you again. Give me one moment! :books:

#

If you don't have a config export in your proxy.ts, your proxy runs on every single request — including all RSC navigation requests (_rsc=...). This is almost certainly the root cause of your "router state header could not be parsed" errors.

Without a matcher, Next.js Proxy defaults to running on all routes. The fix is to add a config export to restrict which paths trigger your proxy. [proxy convention]

Add this to your proxy.ts:

export const config = {
  matcher: [
    /*
     * Match all request paths except for the ones starting with:
     * - api (API routes)
     * - _next/static (static files)
     * - _next/image (image optimization files)
     * - favicon.ico, sitemap.xml, robots.txt (metadata files)
     */
    '/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)',
  ],
};

[negative matching]

This ensures your locale-detection logic still runs on all page routes, but skips static assets and Next.js internals — which is where the RSC navigation interference was happening. The correlation between a broad (or absent) matcher and these RSC errors is well documented. [RSC navigation failures]

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@young hazel Kapa.ai is still learning and improving, please let me know how I did by reacting below.

#

Let me check the knowledge base for you again. Give me one moment! :books:

#

Yes, the matcher pattern I suggested will still run your proxy on _rsc requests, because _rsc is just a query parameter (e.g. /some-page?_rsc=abc123) — the path itself matches the pattern.

However, the Next.js docs note an important behavior: during RSC requests, Next.js automatically strips internal Flight headers (rsc, next-router-state-tree, next-router-prefetch) from the request instance inside Proxy. And when you use NextResponse.rewrite(), Next.js automatically propagates the required RSC rewrite headers upstream. [setting headers]

So the rewrite itself should be safe. The concern is more about:

  1. Whether your buildRequestHeaders accidentally drops or corrupts headers that RSC navigation depends on — since you're cloning and rebuilding headers, make sure you're spreading the original headers first and only adding/overriding your custom ones:
function buildRequestHeaders(request: NextRequest, ...) {
  const headers = new Headers(request.headers); // preserve originals
  headers.set('x-your-custom-header', someValue);
  return headers;
}
  1. Whether attachLocaleCookie sets any response headers with non-ASCII or malformed values.

The matcher alone won't prevent your proxy from running on RSC requests — but as long as you're not corrupting the original headers and you're using NextResponse.rewrite() (not a custom fetch), RSC propagation should work correctly. [setting headers]

Can you share the buildRequestHeaders function so I can check if headers are being preserved correctly?

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@young hazel Kapa.ai is still learning and improving, please let me know how I did by reacting below.

#

Let me check the knowledge base for you again. Give me one moment! :books:

#

Based on the knowledge sources, the "router state header was sent but could not be parsed" error in the context of proxy.ts and RSC navigations was reported specifically against Next.js 16.2.0, which appears to be when it became more prominent. [RSC navigation failures]

There are also related regression issues reported starting from 16.2.2 involving rewrites and multi-zone setups producing blank pages and broken routing behavior. [multi-zone blank page]

The knowledge sources don't explicitly confirm whether this error existed before 16.2.0 in your specific proxy/rewrite scenario, but the pattern is clear: 16.2.x introduced regressions in how proxy/rewrite/RSC interactions are handled compared to 16.1.7 and earlier.

Notably, one issue specifically calls out that 16.1.7 works correctly while 16.2.2 does not for rewrite/proxy setups. [multi-zone blank page]

Given you upgraded from 16.1.6 → 16.2.4, this timing aligns exactly with the reported regressions introduced in the 16.2.x line. The knowledge sources don't contain enough detail about whether 16.2.4 specifically fixes these issues, so I'd recommend checking the Next.js changelog or considering whether reverting to 16.1.7 is a viable short-term option while the upstream fixes are confirmed stable.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@young hazel Kapa.ai is still learning and improving, please let me know how I did by reacting below.

#

Let me check the knowledge base for you again. Give me one moment! :books:

#

Based on the knowledge sources, here's what's confirmed about when the memory leak was fixed:

Memory Leak Fix Timeline

The three bugs causing the ArrayBuffer/OOM memory leak were:

  • PR #88577 — fix for clone-response.js (both tee'd stream branches now cancelled)
  • PR #89040 — fix for LRU cache zero-size entries
  • PR #90771 — partial fix for response pipeline retention

PR #88577 was merged into canary as early as 16.2.0-canary.34, and the fix was confirmed working in 16.2.0-canary.79 — users verified that arrayBuffers returned to baseline after load stopped. [canary fix confirmed]

The fix then landed in the stable 16.2.0 release, as confirmed by a Next.js maintainer who closed a backport request stating that commit 53481e3 (the merge for PR #88577) was already included in 16.2.0. [backport closed]

So in short:

  • 16.1.6 — leaks (your previous version)
  • 16.2.0+ — memory leak fixed

Since you've already upgraded to 16.2.4, the memory leak should be resolved. However, as you've now discovered, 16.2.x introduced the RSC router state header regression — so you're trading one problem for another. [ArrayBuffer retention]

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon:

#

@young hazel Kapa.ai is still learning and improving, please let me know how I did by reacting below.

#

Let me check the knowledge base for you again. Give me one moment! :books:

#

Based on the knowledge sources, there is no single stable version that has both fixes simultaneously:

  • 16.1.x and earlier — memory leak is not fixed (the fix landed in 16.2.0)
  • 16.2.0+ — memory leak is fixed, but the RSC router state header regression was introduced in 16.2.0

The knowledge sources confirm that 16.1.7 is specifically called out as working correctly for proxy/rewrite/RSC scenarios (while 16.2.0+ breaks it) [RSC navigation failures], but 16.1.7 still has the memory leak.

Unfortunately, the knowledge sources do not contain evidence of a version that resolves both issues. Your options as described in the sources are:

  1. Stay on 16.2.4 (memory leak fixed) and work around the RSC header issue by fixing your proxy.ts header handling — this is likely the better long-term path.
  2. Downgrade to 16.1.7 (RSC issue resolved) but accept the memory leak and mitigate it with container restarts or memory limits.

Given that the RSC error in your case may be fixable at the proxy.ts level (preserving original headers in buildRequestHeaders), I'd recommend staying on 16.2.4 and investigating the header handling rather than downgrading back into the memory leak.

Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: