Skip to content

fix(fetch-cache): decode residual stacked compression on Workers - #2984

Draft
james-elicx wants to merge 2 commits into
mainfrom
codex/fix-cloudflare-stacked-content-encoding
Draft

fix(fetch-cache): decode residual stacked compression on Workers#2984
james-elicx wants to merge 2 commits into
mainfrom
codex/fix-cloudflare-stacked-content-encoding

Conversation

@james-elicx

@james-elicx james-elicx commented Aug 18, 2026

Copy link
Copy Markdown
Member

Summary

  • install a Cloudflare fetch transport adapter before user server modules evaluate
  • reproduce Node/Undici content decoding on Workers by fetching raw encoded bodies and decoding the complete Content-Encoding chain lazily
  • preserve Next-observable response headers, URL, redirect state, type, cloning behavior, and explicit Workers manual-body passthrough
  • bump the fetch-cache key prefix to v6 so older partially decoded cache entries cannot be replayed

Fixes #2983.

Root cause

The failing origin emits stacked content codings. Node's native fetch, which Next.js uses through originFetch, decodes the complete chain while retaining the response headers. Workerd's automatic decoder currently handles only a single gzip or br coding, so a stacked response can expose residual compressed bytes to response.json() and to vinext's binary-safe fetch cache.

The adapter keeps the generic fetch-cache implementation platform-neutral. On Cloudflare Workers it:

  • uses Workerd's encodeResponseBody: "manual" transport mode
  • sends Node's default Accept-Encoding: gzip, deflate when the caller supplied none
  • decodes codings in reverse application order with streaming Node-compatible codecs
  • supports repeated codings and concatenated gzip members
  • defers body reads until the caller consumes the response, so fetch() still resolves at headers
  • leaves explicit encodeResponseBody: "manual" calls untouched

Next.js reference: https://github.com/vercel/next.js/blob/canary/packages/next/src/server/lib/patch-fetch.ts

Validation

  • vp check packages/vinext/src/server/cloudflare-fetch-adapter.ts packages/vinext/src/server/server-globals.ts packages/vinext/src/shims/fetch-cache.ts tests/cloudflare-fetch-adapter.test.ts tests/cloudflare-fetch-cache-integration.test.ts tests/fetch-cache.test.ts
  • vp test run tests/cloudflare-fetch-adapter.test.ts tests/cloudflare-fetch-cache-integration.test.ts tests/fetch-cache.test.ts tests/kv-cache-handler.test.ts tests/isr-cache.test.ts tests/edge-globals.test.ts (293 passed)
  • vp run vinext#build
  • local Workerd against the issue's live origin: default fetch and caller-supplied Accept-Encoding: gzip both decoded the full gzip, gzip response and returned JSON successfully
  • four independent cumulative reviews; final runtime, Workers-platform, and parity/security/performance verdicts: no blocking findings

@pkg-pr-new

pkg-pr-new Bot commented Aug 18, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@vinext/cloudflare@2984
npm i https://pkg.pr.new/create-vinext-app@2984
npm i https://pkg.pr.new/@vinext/types@2984
npm i https://pkg.pr.new/vinext@2984

commit: 05471d5

@github-actions

Copy link
Copy Markdown
Contributor
Example Preview Production Original
app-router-cloudflare preview production
pages-router-cloudflare preview production
app-router-playground preview production original
realworld-api-rest preview production
nextra-docs-template preview production
benchmarks preview production
hackernews preview production original
workers-cache preview production
web preview production

@github-actions

github-actions Bot commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Performance benchmarks

Compared 05471d5 against base f4df1c1 using alternating same-runner rounds. Next.js was unchanged and skipped.

0 improved · 0 regressed · 6 within ±1.5%

Scenario Framework Baseline Current Change
Client bundle size (gzip) vinext 140.3 KB 140.3 KB ⚫ -0.0%
Client entry size (gzip) vinext 127.7 KB 127.7 KB ⚫ -0.0%
Dev server cold start vinext 3.11 s 3.08 s ⚫ -1.0%
Production build time vinext 3.54 s 3.55 s ⚫ +0.1%
RSC entry closure size (gzip) vinext 115.3 KB 116.3 KB ⚫ +0.8%
Server bundle size (gzip) vinext 196.5 KB 198.2 KB ⚫ +0.8%

View detailed results and traces

🟢 improvement · 🔴 regression · ⚫ change below 1.5% · paired base/head

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Can't run sample project that uses ISR on Cloudflare via @vinext/cloudflare 1.0.0-beta.6

1 participant