Simon Hearne

Web Performance Consultant

Email Twitter LinkedIn Github RSS

Fixing empty responses from Cloudflare Workers Sites

Published

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
    response.headers.delete('Last-Modified')

    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.


Work with me!

I'm currrently available to consult - from web performance workshops to reviewing new site designs, third-party audits to global performance assessments.
Head over to my Consultancy page to see the kind of work I help my clients with and for details on how to get started.