Awesome Reviewers

When writing caching-related middleware (cache, etag, compress), never assume a cloned or cached Response has mutable headers. Responses returned directly from fetch() or retrieved from the Cache API are immutable in many runtimes (Cloudflare Workers, Deno), so calling .clone() and then headers.set()/delete() will throw TypeError: Headers are immutable. Instead, construct a brand-new Response when you need to modify headers:

const res = new Response(c.res.clone().body, {
  status: c.res.status,
  headers: c.res.headers,
})
res.headers.set(cachedTime, String(Date.now()))

Only create the new Response when mutation is actually needed (e.g. wrap the headers.set/delete call in a try/catch and fall back to constructing a new Response on an “immutable” TypeError) to avoid unnecessary overhead on the common path. Apply the same pattern when stripping/retaining headers (e.g. ETag middleware building a 304 response) — only keep headers defined by spec (Cache-Control, Content-Location, Date, ETag, Expires, Vary) and avoid re-wrapping a response unless necessary, since an extra wrap can unexpectedly empty the body in some runtimes.

Additionally, when parsing or emitting cache-related headers (Cache-Control, Vary), follow the relevant RFCs precisely:

Following this pattern prevents runtime crashes across different hosting environments and ensures cache behavior is spec-compliant and consistent.