Fixing empty responses from 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.
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.