nextjs - ✅(Solved) Fix Memory leak: ArrayBuffer/WriteWrap retention in 16.1.6 standalone (fixed in canary, not released) [1 pull requests, 4 comments, 3 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
vercel/next.js#90898Fetched 2026-04-08 00:19:17
View on GitHub
Comments
4
Participants
3
Timeline
10
Reactions
0
Timeline (top)
commented ×4labeled ×2closed ×1comment_deleted ×1

Root Cause

20 of 31 large ArrayBuffers retained by WriteWrap._chunks that are never freed because the source ReadableStream tee branches are never cancelled.

Fix Action

Fixed

PR fix notes

PR #90897: fix: backport memory leak fixes (FinalizationRegistry + LRU zero-size) to stable

Description (problem / solution / changelog)

Summary

Backports three critical memory leak fixes from canary to the 16.x stable branch. These fixes prevent ArrayBuffer/WriteWrap retention causing OOM kills in production output: standalone deployments.

Changes

  1. clone-response.ts: Register both tee'd response clones with FinalizationRegistry (from #88577)

    • Previously only cloned1 was registered. cloned2 (stored in caches) was never auto-cancelled, leaving its tee buffer (backed by ArrayBuffers) alive indefinitely.
  2. lru-cache.ts: Enforce minimum size of 1 and throw on zero-size entries (from #89040)

    • calculateSize returning 0 meant entries never incremented totalSize, so the eviction loop (totalSize > maxSize) never fired. The Map grew without bound.
  3. Caller fixes (filesystem.ts, next-dev-server.ts): Ensure all calculateSize callbacks return >= 1

Evidence

Heap dump from production (ARM64 ECS Fargate, Next.js 16.1.6, Node 22.22.0) at 1 GB ArrayBuffers:

  • 977 MB in 183 JSArrayBufferData objects (mostly 32 MB chunks)
  • Retainer chain: WriteWrap._chunks → GC roots (20/31 large ArrayBuffers)
  • Root cause: un-cancelled tee'd ReadableStream branches holding native HTTP write buffers
VersionAfter 60s loadAfter settling
16.1.6 stable663 MB (growing)663 MB+ (never reclaimed, OOM)
canary.791040 MB (peak)32 MB (back to baseline)

Related: #85914, #89033, #90433, #90895

Test plan

  • Existing lru-cache.test.ts updated for new set() return type and zero-size rejection
  • Verified on production ECS Fargate (ARM64): canary.79 with these fixes resolves the leak completely

Made with Cursor

Changed files

  • .gitattributes (modified, +3/-0)
  • .github/workflows/build_and_deploy.yml (modified, +2/-2)
  • .github/workflows/build_and_test.yml (modified, +7/-18)
  • .github/workflows/build_reusable.yml (modified, +8/-0)
  • .github/workflows/trigger_release.yml (modified, +3/-4)
  • Cargo.lock (modified, +260/-172)
  • Cargo.toml (modified, +10/-10)
  • crates/next-build-test/nextConfig.json (modified, +1/-0)
  • crates/next-custom-transforms/tests/loader/issue-30389/output.js (modified, +2/-2)
  • docs/01-app/01-getting-started/04-linking-and-navigating.mdx (modified, +1/-1)
  • docs/01-app/01-getting-started/05-server-and-client-components.mdx (modified, +187/-2)
  • docs/01-app/01-getting-started/06-cache-components.mdx (modified, +15/-6)
  • docs/01-app/01-getting-started/07-fetching-data.mdx (modified, +2/-2)
  • docs/01-app/01-getting-started/08-updating-data.mdx (modified, +4/-2)
  • docs/01-app/01-getting-started/13-fonts.mdx (modified, +214/-0)
  • docs/01-app/01-getting-started/16-proxy.mdx (modified, +2/-2)
  • docs/01-app/02-guides/content-security-policy.mdx (modified, +3/-1)
  • docs/01-app/02-guides/package-bundling.mdx (modified, +175/-34)
  • docs/01-app/02-guides/production-checklist.mdx (modified, +1/-1)
  • docs/01-app/02-guides/public-static-pages.mdx (added, +253/-0)
  • docs/01-app/02-guides/redirecting.mdx (modified, +9/-9)
  • docs/01-app/02-guides/scripts.mdx (modified, +4/-4)
  • docs/01-app/02-guides/upgrading/version-16.mdx (modified, +5/-3)
  • docs/01-app/03-api-reference/01-directives/use-cache-private.mdx (modified, +31/-7)
  • docs/01-app/03-api-reference/01-directives/use-cache.mdx (modified, +33/-1)
  • docs/01-app/03-api-reference/02-components/image.mdx (modified, +23/-0)
  • docs/01-app/03-api-reference/03-file-conventions/dynamic-routes.mdx (modified, +1/-1)
  • docs/01-app/03-api-reference/03-file-conventions/layout.mdx (modified, +4/-2)
  • docs/01-app/03-api-reference/04-functions/after.mdx (modified, +2/-2)
  • docs/01-app/03-api-reference/04-functions/cookies.mdx (modified, +9/-9)
  • docs/01-app/03-api-reference/04-functions/forbidden.mdx (modified, +1/-1)
  • docs/01-app/03-api-reference/04-functions/generate-metadata.mdx (modified, +64/-0)
  • docs/01-app/03-api-reference/04-functions/generate-viewport.mdx (modified, +46/-0)
  • docs/01-app/03-api-reference/04-functions/permanentRedirect.mdx (modified, +1/-1)
  • docs/01-app/03-api-reference/04-functions/redirect.mdx (modified, +1/-1)
  • docs/01-app/03-api-reference/04-functions/unauthorized.mdx (modified, +1/-1)
  • docs/01-app/03-api-reference/05-config/01-next-config-js/optimizePackageImports.mdx (modified, +1/-0)
  • docs/01-app/03-api-reference/06-cli/next.mdx (modified, +48/-9)
  • docs/01-app/04-glossary.mdx (added, +272/-0)
  • docs/02-pages/04-api-reference/03-functions/use-params.mdx (added, +242/-0)
  • docs/02-pages/04-api-reference/03-functions/use-search-params.mdx (added, +318/-0)
  • docs/02-pages/04-api-reference/04-config/01-next-config-js/optimizePackageImports.mdx (modified, +1/-0)
  • errors/next-prerender-dynamic-metadata.mdx (modified, +4/-4)
  • errors/next-prerender-dynamic-viewport.mdx (modified, +4/-4)
  • lerna.json (modified, +3/-2)
  • packages/create-next-app/helpers/get-pkg-manager.ts (modified, +34/-0)
  • packages/create-next-app/package.json (modified, +1/-1)
  • packages/create-next-app/templates/index.ts (modified, +24/-18)
  • packages/eslint-config-next/package.json (modified, +2/-2)
  • packages/eslint-plugin-internal/package.json (modified, +1/-1)
  • packages/eslint-plugin-next/package.json (modified, +1/-1)
  • packages/font/package.json (modified, +1/-1)
  • packages/next-bundle-analyzer/package.json (modified, +1/-1)
  • packages/next-codemod/package.json (modified, +1/-1)
  • packages/next-env/package.json (modified, +1/-1)
  • packages/next-mdx/package.json (modified, +1/-1)
  • packages/next-plugin-storybook/package.json (modified, +1/-1)
  • packages/next-polyfill-module/package.json (modified, +1/-1)
  • packages/next-polyfill-nomodule/package.json (modified, +1/-1)
  • packages/next-rspack/package.json (modified, +1/-1)
  • packages/next-swc/package.json (modified, +1/-1)
  • packages/next/errors.json (modified, +3/-1)
  • packages/next/package.json (modified, +7/-7)
  • packages/next/src/build/index.ts (modified, +6/-2)
  • packages/next/src/build/templates/app-page.ts (modified, +4/-0)
  • packages/next/src/build/templates/edge-ssr-app.ts (modified, +4/-0)
  • packages/next/src/build/utils.ts (modified, +23/-3)
  • packages/next/src/client/components/segment-cache/lru.ts (modified, +2/-0)
  • packages/next/src/compiled/react-server-dom-turbopack-experimental/cjs/react-server-dom-turbopack-client.browser.development.js (modified, +129/-110)
  • packages/next/src/compiled/react-server-dom-turbopack-experimental/cjs/react-server-dom-turbopack-client.browser.production.js (modified, +67/-55)
  • packages/next/src/compiled/react-server-dom-turbopack-experimental/cjs/react-server-dom-turbopack-client.edge.development.js (modified, +127/-108)
  • packages/next/src/compiled/react-server-dom-turbopack-experimental/cjs/react-server-dom-turbopack-client.edge.production.js (modified, +67/-55)
  • packages/next/src/compiled/react-server-dom-turbopack-experimental/cjs/react-server-dom-turbopack-client.node.development.js (modified, +127/-108)
  • packages/next/src/compiled/react-server-dom-turbopack-experimental/cjs/react-server-dom-turbopack-client.node.production.js (modified, +67/-55)
  • packages/next/src/compiled/react-server-dom-turbopack-experimental/cjs/react-server-dom-turbopack-server.browser.development.js (modified, +603/-342)
  • packages/next/src/compiled/react-server-dom-turbopack-experimental/cjs/react-server-dom-turbopack-server.browser.production.js (modified, +578/-307)
  • packages/next/src/compiled/react-server-dom-turbopack-experimental/cjs/react-server-dom-turbopack-server.edge.development.js (modified, +606/-343)
  • packages/next/src/compiled/react-server-dom-turbopack-experimental/cjs/react-server-dom-turbopack-server.edge.production.js (modified, +581/-308)
  • packages/next/src/compiled/react-server-dom-turbopack-experimental/cjs/react-server-dom-turbopack-server.node.development.js (modified, +612/-347)
  • packages/next/src/compiled/react-server-dom-turbopack-experimental/cjs/react-server-dom-turbopack-server.node.production.js (modified, +587/-312)
  • packages/next/src/compiled/react-server-dom-turbopack-experimental/package.json (modified, +2/-2)
  • packages/next/src/compiled/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.browser.development.js (modified, +129/-110)
  • packages/next/src/compiled/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.browser.production.js (modified, +67/-55)
  • packages/next/src/compiled/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.edge.development.js (modified, +127/-108)
  • packages/next/src/compiled/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.edge.production.js (modified, +67/-55)
  • packages/next/src/compiled/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.node.development.js (modified, +127/-108)
  • packages/next/src/compiled/react-server-dom-turbopack/cjs/react-server-dom-turbopack-client.node.production.js (modified, +67/-55)
  • packages/next/src/compiled/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.development.js (modified, +603/-342)
  • packages/next/src/compiled/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.browser.production.js (modified, +578/-307)
  • packages/next/src/compiled/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.development.js (modified, +606/-343)
  • packages/next/src/compiled/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.edge.production.js (modified, +0/-0)
  • packages/next/src/compiled/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.development.js (modified, +0/-0)
  • packages/next/src/compiled/react-server-dom-turbopack/cjs/react-server-dom-turbopack-server.node.production.js (modified, +0/-0)
  • packages/next/src/compiled/react-server-dom-turbopack/package.json (modified, +0/-0)
  • packages/next/src/compiled/react-server-dom-webpack-experimental/cjs/react-server-dom-webpack-client.browser.development.js (modified, +0/-0)
  • packages/next/src/compiled/react-server-dom-webpack-experimental/cjs/react-server-dom-webpack-client.browser.production.js (modified, +0/-0)
  • packages/next/src/compiled/react-server-dom-webpack-experimental/cjs/react-server-dom-webpack-client.edge.development.js (modified, +0/-0)
  • packages/next/src/compiled/react-server-dom-webpack-experimental/cjs/react-server-dom-webpack-client.edge.production.js (modified, +0/-0)
  • packages/next/src/compiled/react-server-dom-webpack-experimental/cjs/react-server-dom-webpack-client.node.development.js (modified, +0/-0)
  • packages/next/src/compiled/react-server-dom-webpack-experimental/cjs/react-server-dom-webpack-client.node.production.js (modified, +0/-0)

Code Example

JSArrayBufferData (32 MB)
ArrayBufferBufferArray[4]
WriteWrap._chunksGC roots (Global handles)

---

Node: v22.22.0
Next.js: 16.1.6 (leaks)16.2.0-canary.79 (fixed)
output: standalone
RAW_BUFFERClick to expand / collapse

Link to the code that reproduces this issue

https://github.com/Tib-Gridello/nextjs-memory-leak-repro

To Reproduce

  1. docker build -t nextjs-leak .
  2. docker run -p 3000:3000 --memory=4g nextjs-leak
  3. Send traffic: bash scripts/load.sh http://localhost:3000 120
  4. Monitor: bash scripts/monitor.sh http://localhost:3000

Current vs. Expected behavior

Current (16.1.6): arrayBuffers grows linearly at ~5 MB/s under normal request traffic and is never reclaimed, even with --expose-gc and manual global.gc(). OOM within minutes.

Expected: Memory should stabilize and be reclaimed after requests complete.

Verified fix

Changing package.json to "next": "16.2.0-canary.79" completely resolves the leak — arrayBuffers returns to baseline (32 MB) after load stops.

The relevant canary fixes are #88577 (register both tee'd clones with FinalizationRegistry) and #89040 (enforce minimum size in LRU cache). Neither is in any 16.x stable release.

Heap dump evidence

Heap snapshot captured at 1 GB of ArrayBuffers shows 977 MB retained in 183 JSArrayBufferData objects. Dominant retainer chain:

JSArrayBufferData (32 MB)
  ← ArrayBuffer ← Buffer ← Array[4]
    ← WriteWrap._chunks ← GC roots (Global handles)

20 of 31 large ArrayBuffers retained by WriteWrap._chunks that are never freed because the source ReadableStream tee branches are never cancelled.

Provide environment information

Node: v22.22.0
Next.js: 16.1.6 (leaks) → 16.2.0-canary.79 (fixed)
output: standalone

Which area(s) are affected? (Select all that apply)

Performance

Which stage(s) are affected? (Select all that apply)

Other (Deployed)

extent analysis

Fix: Upgrade Next.js to a version that includes the canary fixes (≥ 16.2.0‑canary.79). If you must stay on 16.1.x, back‑port the two patches manually.


1️⃣ Upgrade (preferred)

# package.json
{
  "dependencies": {
    "next": "16.2.0-canary.79",   # or any later 16.2.x release
    ...
  }
}
# reinstall & rebuild
npm ci            # or yarn install --frozen-lockfile
docker build -t nextjs-leak .
docker run -p 3000:3000 --memory=4g nextjs-leak

The leak disappears because:

  • #88577 – both tee‑ed ReadableStream branches are registered with FinalizationRegistry, allowing the

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

nextjs - ✅(Solved) Fix Memory leak: ArrayBuffer/WriteWrap retention in 16.1.6 standalone (fixed in canary, not released) [1 pull requests, 4 comments, 3 participants]