Fixing empty responses from Cloudflare Workers Sites

A quick fix for empty response bodies on Cloudflare Workers Sites

I'm currently working on a client project using Cloudflare Workers Sites. Mostly development has been a joy, the wrangler CLI is easy to use and production deployment happens in seconds.

There was one strange issue reported by the client which left me looking for answers: sometimes the pages wouldn't render, other times no styles were applied. Occasionally images wouldn't render.

These issues were obviously critical, and I managed to reproduce them only on the production deployment but not with wrangler dev. The cause of the broken pages was empty responses — occasionally a response would appear to be successful (200 etc.) but there was no content in the body. I had some custom code messing with headers in the document handler, but nothing that modified the CSS or image responses.

The biggest clue to the cause of the issue is that it never occurred with a hard reload, only normal navigations or soft reloads.

It turns out that in this Workers Sites deployment a last-modified header is added to responses by default (there's no code in the bundle which does this). As described in my blog post on caching, this header allows the browser to emit a conditional GET request with the if-modified-since header. The correct response from the server is a 200 with the full response if the browser's timestamp is earlier than the server's, or a 304 empty response if the browser has the latest version of the asset.

It appears that this Workers Sites deployment does not correctly respond with a 304 — instead it emits an empty 200 which causes all of the issues we had observed.

screenshot of developer tools showing a 200 response with an empty body, with last modified response header and if modified since request header
Empty 200 responses when if-modified-since request header set.

I'm not a fan of last-modified as a caching strategy (especially without a cache-control directive) so my quick fix was to delete this response header in the event handler function in index.js:

async function handleEvent(event) {
const url = new URL(event.request.url)
let options = {}
try {
const page = await getAssetFromKV(event, options)
const response = new Response(page.body, page)

response.headers.set('X-XSS-Protection', '1; mode=block')
response.headers.set('X-Content-Type-Options', 'nosniff')
response.headers.set('X-Frame-Options', 'DENY')
response.headers.set('Referrer-Policy', 'unsafe-url')
response.headers.set('Feature-Policy', 'none')

// delete the Last-Modified response header

return response

I still need to implement a correct caching strategy, but at least the site works consistently now!

I've reviewed other Workers Sites properties that I manage and the last-modified response header is not set on those. I'm not sure what is different with this property except that it is the most recent new deployment.