[PR #111] [MERGED] fix: fetch cache key generation — handle all body types, include all headers minus blocklist #319

Closed
opened 2026-05-06 12:39:10 +02:00 by BreizhHardware · 0 comments

📋 Pull Request Information

Original PR: https://github.com/cloudflare/vinext/pull/111
Author: @threepointone
Created: 2/26/2026
Status: Merged
Merged: 2/26/2026
Merged by: @threepointone

Base: mainHead: fix/fetch-cache-key-generation


📝 Commits (3)

  • b420216 fix: fetch cache key generation — handle all body types, include all headers minus blocklist
  • 84a5ca7 Improve fetch cache key generation
  • 3d5c73d PR #111 approved. 52 tests pass, 3 fixes correct.

📊 Changes

3 files changed (+871 additions, -266 deletions)

View changed files

📝 packages/vinext/src/shims/fetch-cache.ts (+163 -56)
📝 tests/fetch-cache.test.ts (+414 -8)
📝 tests/fixtures/pages-basic/dist/server/entry.js (+294 -202)

📄 Description

Summary

Fixes two bugs in the fetch cache key generation and adds a third bug fix discovered during review.

Bug 1: Binary/FormData bodies ignored in cache key

buildFetchCacheKey only handled string bodies. Requests with Uint8Array, ReadableStream, FormData, Blob, or URLSearchParams bodies all produced the same cache key regardless of body content — causing incorrect cache hits.

Fix: serializeBody() now handles all BodyInit types. buildFetchCacheKey is now async to support stream/blob consumption.

Bug 2: Header inclusion strategy inverted vs Next.js

We used an allowlist of 3 auth headers (authorization, cookie, x-api-key). Next.js uses a blocklist (traceparent, tracestate) and includes everything else. This meant headers like Accept, Accept-Language, and custom headers were ignored — different requests could incorrectly share cache entries.

Fix: Switched to blocklist approach matching Next.js. All headers are now included in the cache key except W3C trace context headers.

Bug 3: Consumed stream body sent to network (found during review)

After serializeBody consumed a ReadableStream body for cache key generation, the spent stream was still passed to originalFetch(). The reconstructed body (_ogBody) was stashed but never restored.

Fix: stripNextFromInit now restores _ogBody as the body before passing to originalFetch.

Additional improvements

  • Cache key includes mode, redirect, credentials, referrer, referrerPolicy, integrity, cache from RequestInit
  • Key is SHA-256 hashed (matching Next.js) with CACHE_KEY_PREFIX = "v1" for future cache-busting
  • URLSearchParams handled separately from FormData (different APIs)

Tests

19 new tests (52 total):

  • 7 body type cache key tests (string, Uint8Array, Blob, FormData, ReadableStream)
  • 6 header inclusion tests (Accept, custom headers, trace header exclusion, order independence)
  • 4 body restoration tests (verify originalFetch gets usable body after key generation)
  • 2 URLSearchParams tests

Not breaking

All changes are internal. Public API unchanged. Only user-visible effect: one-time cold cache after upgrade (new key format).


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/cloudflare/vinext/pull/111 **Author:** [@threepointone](https://github.com/threepointone) **Created:** 2/26/2026 **Status:** ✅ Merged **Merged:** 2/26/2026 **Merged by:** [@threepointone](https://github.com/threepointone) **Base:** `main` ← **Head:** `fix/fetch-cache-key-generation` --- ### 📝 Commits (3) - [`b420216`](https://github.com/cloudflare/vinext/commit/b42021660302940268da26b3fd7500ccc76e55c0) fix: fetch cache key generation — handle all body types, include all headers minus blocklist - [`84a5ca7`](https://github.com/cloudflare/vinext/commit/84a5ca76f7250b7995bf87d39532b4e8943b8fd3) Improve fetch cache key generation - [`3d5c73d`](https://github.com/cloudflare/vinext/commit/3d5c73d4eb014bd37b74af23871660955abc8091) PR #111 approved. 52 tests pass, 3 fixes correct. ### 📊 Changes **3 files changed** (+871 additions, -266 deletions) <details> <summary>View changed files</summary> 📝 `packages/vinext/src/shims/fetch-cache.ts` (+163 -56) 📝 `tests/fetch-cache.test.ts` (+414 -8) 📝 `tests/fixtures/pages-basic/dist/server/entry.js` (+294 -202) </details> ### 📄 Description ## Summary Fixes two bugs in the fetch cache key generation and adds a third bug fix discovered during review. ### Bug 1: Binary/FormData bodies ignored in cache key `buildFetchCacheKey` only handled `string` bodies. Requests with `Uint8Array`, `ReadableStream`, `FormData`, `Blob`, or `URLSearchParams` bodies all produced the same cache key regardless of body content — causing incorrect cache hits. **Fix:** `serializeBody()` now handles all `BodyInit` types. `buildFetchCacheKey` is now async to support stream/blob consumption. ### Bug 2: Header inclusion strategy inverted vs Next.js We used an **allowlist** of 3 auth headers (`authorization`, `cookie`, `x-api-key`). Next.js uses a **blocklist** (`traceparent`, `tracestate`) and includes everything else. This meant headers like `Accept`, `Accept-Language`, and custom headers were ignored — different requests could incorrectly share cache entries. **Fix:** Switched to blocklist approach matching Next.js. All headers are now included in the cache key except W3C trace context headers. ### Bug 3: Consumed stream body sent to network (found during review) After `serializeBody` consumed a `ReadableStream` body for cache key generation, the spent stream was still passed to `originalFetch()`. The reconstructed body (`_ogBody`) was stashed but never restored. **Fix:** `stripNextFromInit` now restores `_ogBody` as the body before passing to `originalFetch`. ### Additional improvements - Cache key includes `mode`, `redirect`, `credentials`, `referrer`, `referrerPolicy`, `integrity`, `cache` from `RequestInit` - Key is SHA-256 hashed (matching Next.js) with `CACHE_KEY_PREFIX = "v1"` for future cache-busting - `URLSearchParams` handled separately from `FormData` (different APIs) ### Tests 19 new tests (52 total): - 7 body type cache key tests (string, Uint8Array, Blob, FormData, ReadableStream) - 6 header inclusion tests (Accept, custom headers, trace header exclusion, order independence) - 4 body restoration tests (verify `originalFetch` gets usable body after key generation) - 2 URLSearchParams tests ### Not breaking All changes are internal. Public API unchanged. Only user-visible effect: one-time cold cache after upgrade (new key format). --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
BreizhHardware 2026-05-06 12:39:10 +02:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/vinext#319
No description provided.