nextjs - ✅(Solved) Fix Memory regression after `15.4.0`: combining `cookies()` with server `fetch()` to a JSON endpoint causes rapid heap growth and OOM under load [1 pull requests, 12 comments, 4 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#90829Fetched 2026-04-08 00:19:25
View on GitHub
Comments
12
Participants
4
Timeline
30
Reactions
8
Timeline (top)
commented ×12labeled ×5subscribed ×5mentioned ×3

Error Message

This fails after roughly 2 to 3 minutes with a JavaScript heap out-of-memory error.

  • 15.4.1 and 16.1.6 show rapid memory growth and crash with a JavaScript heap out-of-memory error

Root Cause

This makes it look like the issue is caused by the interaction between these features, rather than by any one of them in isolation.

Fix Action

Fixed

PR fix notes

PR #90851: fix: consume fetch response bodies in dedupe-fetch to prevent memory

Description (problem / solution / changelog)

What?

  • Fixes a memory regression where combining cookies() with server-side fetch() to a JSON endpoint caused heap growth and OOM under load.

Why?

  • Unconsumed fetch response bodies kept streams and buffers alive, so memory grew with each request. This showed up when both cookies() and fetch() were used together.

How?

  • In dedupe-fetch, we consume response bodies before cloning for responses ≤1MB (including chunked responses without Content-Length). Consuming releases the connection and avoids unbounded buffering. Responses with Content-Length >1MB still use the original stream-based path to avoid buffering large bodies.

Fixes #90829

Changed files

  • packages/next/src/server/lib/dedupe-fetch.ts (modified, +74/-6)

Code Example

npm i -E next@15.4.0
bash ./scripts/perf/run-docker-memory-benchmark.sh --label next15_4_0

---

npm i -E next@15.4.1
bash ./scripts/perf/run-docker-memory-benchmark.sh --label next15_4_1

---

npm i -E next@16.1.6
bash ./scripts/perf/run-docker-memory-benchmark.sh --label next16_1_6

---

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 25.3.0: Wed Jan 28 20:48:41 PST 2026; root:xnu-12377.81.4~5/RELEASE_ARM64_T6041
  Available memory (MB): 36864
  Available CPU cores: 14
Binaries:
  Node: 22.20.0
  npm: 10.9.3
  Yarn: N/A
  pnpm: 9.15.4
Relevant Packages:
  next: 16.1.6 // Latest available version is detected (16.1.6).
  eslint-config-next: N/A
  react: 19.2.3
  react-dom: 19.2.3
  typescript: 5.9.3
Next.js Config:
  output: N/A
RAW_BUFFERClick to expand / collapse

Link to the code that reproduces this issue

https://github.com/AlanChauchet/test-next-memory

To Reproduce

Using the reproduction repo:

npm i -E [email protected]
bash ./scripts/perf/run-docker-memory-benchmark.sh --label next15_4_0

This completes successfully for the full 25-minute benchmark.

npm i -E [email protected]
bash ./scripts/perf/run-docker-memory-benchmark.sh --label next15_4_1

This fails after roughly 2 to 3 minutes with a JavaScript heap out-of-memory error.

npm i -E [email protected]
bash ./scripts/perf/run-docker-memory-benchmark.sh --label next16_1_6

This fails in the same way as 15.4.1.

Current vs. Expected behavior

Expected:

  • 15.4.1 and later should behave similarly to 15.4.0
  • memory usage should stay bounded under sustained load
  • the app should complete the benchmark without a heap crash

Actual:

  • 15.4.0 completes the benchmark
  • 15.4.1 and 16.1.6 show rapid memory growth and crash with a JavaScript heap out-of-memory error
  • in the working case, container memory usage generally stays under about 20%
  • in the failing cases, memory usage reaches around 70% in less than 2 minutes before continuing toward the crash

Provide environment information

Operating System:
  Platform: darwin
  Arch: arm64
  Version: Darwin Kernel Version 25.3.0: Wed Jan 28 20:48:41 PST 2026; root:xnu-12377.81.4~5/RELEASE_ARM64_T6041
  Available memory (MB): 36864
  Available CPU cores: 14
Binaries:
  Node: 22.20.0
  npm: 10.9.3
  Yarn: N/A
  pnpm: 9.15.4
Relevant Packages:
  next: 16.1.6 // Latest available version is detected (16.1.6).
  eslint-config-next: N/A
  react: 19.2.3
  react-dom: 19.2.3
  typescript: 5.9.3
Next.js Config:
  output: N/A

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

Loading UI and Streaming, Cookies, Performance

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

next start (local), Other (Deployed)

Additional context

Minimal reproduction details

The app is intentionally tiny. The route under test is a server component page that does only this on each request:

  • calls cookies() from next/headers
  • performs a server-side fetch()
  • the fetch target returns JSON

The benchmark repeatedly hits that route with randomized query strings to create many distinct request URLs.

Important isolation findings

I tested the pieces independently, and the failure seems to require a specific combination:

  • cookies() + fetch() to the current JSON endpoint: fails on 15.4.1 and 16.1.6
  • remove cookies(), keep fetch(): succeeds on all tested versions
  • remove fetch(), keep cookies(): succeeds on all tested versions
  • keep cookies(), but change fetch() to a URL that does not return JSON: succeeds on all tested versions

So the regression appears only when all of the following are combined:

  • request cookie access via cookies()
  • a server-side fetch()
  • a fetched response that returns JSON

This makes it look like the issue is caused by the interaction between these features, rather than by any one of them in isolation.

In addition, the memory profile tracks with that behavior:

  • the working variants tend to remain under about 20% memory usage
  • the failing variants climb to around 70% in under 2 minutes

Versions tested

  • 15.4.0: works
  • 15.4.1: fails
  • 16.1.6: fails

This suggests a regression introduced between 15.4.0 and 15.4.1 that is still reproducible in 16.1.6.

Additional context

The reproduction repo includes:

  • a Dockerized benchmark runner
  • k6 load generation
  • memory and process sampling
  • per-run artifacts (docker-stats.csv, process-memory.csv, container.log, k6-summary.json, k6-output.log)

If helpful, I can also provide the generated logs and memory traces from successful vs failing runs.

extent analysis

Fix Summary

The leak is caused by Next.js’s built‑in fetch cache using the request’s cookies as part of the cache key.
Every request gets a unique cookie‑aware cache entry (the benchmark adds a random query string), so the server‑side cache grows without bound until the V8 heap blows up.

Solution: Disable the automatic fetch cache for the endpoint that returns JSON (or globally for the whole app) and, if you still need cookies, read them directly from the request headers instead of cookies().


Step‑by‑Step Fix Plan

1. Disable fetch caching for the JSON endpoint

Add the `cache:

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