From 651f344247767a6a4cd28f9e288fb325a8744cb6 Mon Sep 17 00:00:00 2001 From: jedarden Date: Thu, 30 Apr 2026 12:51:28 -0400 Subject: [PATCH] fix(web): decompress .gz objects in Pages Function instead of setting Content-Encoding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cloudflare CDN strips Content-Encoding: gzip from Worker responses even when explicitly set, leaving browsers with raw gzip bytes they cannot parse as JSON. Fix by piping .gz objects through DecompressionStream('gzip') inside the Worker so the response body is always plain JSON — no Content-Encoding header needed. Co-Authored-By: Claude Sonnet 4.6 --- web/functions/r2/[[path]].ts | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/web/functions/r2/[[path]].ts b/web/functions/r2/[[path]].ts index 9a54dd3..7f50a6f 100644 --- a/web/functions/r2/[[path]].ts +++ b/web/functions/r2/[[path]].ts @@ -26,11 +26,14 @@ export const onRequest: PagesFunction = async (context) => { headers.set('Cache-Control', 'public, max-age=60'); headers.set('Access-Control-Allow-Origin', '*'); - // R2 binding strips Content-Encoding when serving object body, even when - // the object was stored with ContentEncoding metadata. Re-apply it so - // browsers know to decompress gzipped objects (.json.gz, .gz). - if (key.endsWith('.gz') && !headers.has('Content-Encoding')) { - headers.set('Content-Encoding', 'gzip'); + // Cloudflare CDN strips Content-Encoding: gzip from Worker responses even + // when we set it, causing browsers to receive raw gzip bytes as JSON. + // Decompress .gz objects inside the Worker instead so the response body is + // always plain, browser-parseable content (no Content-Encoding needed). + if (key.endsWith('.gz')) { + headers.delete('Content-Encoding'); + const decompressed = object.body.pipeThrough(new DecompressionStream('gzip')); + return new Response(decompressed, { headers }); } return new Response(object.body, { headers });