loke.dev
A sealed response envelope being copied into a new editable envelope at the edge

Fix “Can’t Modify Immutable Headers” in Astro on Cloudflare

Why cached Cloudflare Worker responses have immutable headers in Astro middleware, and the safe Response-cloning pattern that prevents the production error.

Published By Loke2 min read

If Astro on Cloudflare throws “Can’t modify immutable headers” only on a cache hit, the cache is often working correctly. The problem is returning its Response object directly into a later framework step that needs to add or finalize headers.

Why a cache hit differs from a fresh response

Cloudflare’s runtime exposes web-standard Response and Headers objects. Responses returned by the Cache API can have immutable headers. Astro middleware, meanwhile, may finalize a response by adding headers after your middleware returns it. Those two behaviors collide only on the cached path.

Return a mutable equivalent

Copy the cached response into a new Response before returning it. This preserves the body, status, status text, and existing headers while giving the downstream framework a mutable header collection.

RequestCache hitCached Response headers are immutablenew Response(...)Astro finalizes
A cache hit can return immutable headers. Clone the response before Astro or middleware attempts to finalize its own headers.
function toMutableResponse(response: Response) {
  return new Response(response.body, {
    status: response.status,
    statusText: response.statusText,
    headers: new Headers(response.headers),
  })
}

const cached = await caches.default.match(cacheKey)
if (cached) return toMutableResponse(cached)

Do not consume the body first

A Response body is a stream. Do not call cached.text(), cached.json(), or cached.arrayBuffer() before passing it into the new Response unless you deliberately create a new body from that data. The example transfers the untouched stream once.

Test the cache-hit path

A local first request can exercise the miss while the second request exercises the hit. In production, inspect the cache behavior and confirm both requests succeed. Test redirects, errors, and non-GET requests separately; they should not accidentally be stored as public page responses.

Do not disable caching to silence the error. Keep the cache hit, but make the response mutable at the boundary where Astro needs to finish it.

For the wider caching and invalidation design, read the fast Astro builds guide.

Reproduce the example

Open the repository

Reference: src/middleware.ts

npm install
Request a cache hit through the deployed Worker and confirm Astro can finalize the returned response without a header mutation error.

Sources and further reading

  1. Middleware · Astro
  2. Headers · Cloudflare
  3. Cache · Cloudflare