A few months ago I documented how I decreased the loading time of a WordPress site in this post. Although those changes made a significant speed improvement the website would still be slow for anybody a long distance from the server as each page had to be grabbed from my origin server instead of being served by Cloudflare CDN (which is where the static page resources are served from).

Over the last 2 months the website has been gaining popularity in countries which are thousands of miles away from the origin server meaning that 500-800 milliseconds wouldn’t be an unusual amount of time to wait for a response from the origin due to the distance the data has to travel. Unfortunately Cloudflare’s Cache Everything mode wouldn’t be viable as that can’t be disabled for users who are logged into the site (on the free plan, at least) and therefore they are served the guest pages instead of the personalised pages after logging in.

Conditional Caching with Cloudflare Workers

Cloudflare Workers allows Javascript code to be run server-side within Cloudflare’s datacenters during the connection between the client and Cloudflare’s regular servers. The code I wrote essentially checks for WordPress cookies and if they’re found then the request is passed through to my origin server as usual, however, if there aren’t any WordPress cookies then Cloudflare’s cache is used. This means that logged out users will get a cached page from their local Cloudflare datacenter and logged in users will be passed straight through to the origin so they get personalised pages.

I used Pingdom Tools to test the speed of the website from Sydney (which is about as far from my origin server as you can be) both with Cloudflare Workers and without. With Workers and caching enabled the page was able to be fetched in 181ms instead of 1305ms for the uncached page; a difference of over one second.


Without Workers enabled; uncached – click to enlarge


With Workers enabled; cached – click to enlarge

The x-cache header is added by my origin and the x-cfw-cache is added by my Javascript Worker.

The code I used is as follows:


//I make no guarantees this code will work reliably. Use at your own risk. Please thoroughly test before deploying.
addEventListener('fetch', event => {
  event.respondWith(handleRequest(event))
})

async function handleRequest(event) {
  let request = event.request;
  let response = null;
  console.log('Got request', request);
  cookies = request.headers.get("Cookie");
  if (cookies && cookies.toLowerCase().includes("wordpress_")){
    console.log("We have cookies. Don't cache.", cookies);
    response = await fetch(request);
    response = new Response(response.body, response);
    response.headers.set("x-cfw-cache", "BYPASS");
    return response;
  }
  let cache = caches.default;
  response = await cache.match(request);
  if (!response){
    console.log("No cache. Will fetch.");
    response = await fetch(request);
    response = new Response(response.body, response);
    let responsecookies = response.headers.get("Set-Cookie");
    if (responsecookies && responsecookies.toLowerCase().includes("wordpress_")){
      //Wordpress cookies are being set here. We don't want to cache.
      response.headers.set("x-cfw-cache", "NO");
    } else {
      
      response.headers.delete("Set-Cookie");
      response.headers.set("x-cfw-cache", "MISS");
      console.log(response.headers.get("Cache-Control"))
      event.waitUntil(cache.put(request, response.clone()))
    }
  } else {
    response = new Response(response.body, response);
    response.headers.set("x-cfw-cache", "HIT");
  }
  
  console.log(response.headers.get("x-cfw-cache"))
  return response
}

Most of my debugging code is still present to make it easier to understand. By enabling Workers on Cloudflare and using the above code, you should get Cloudflare to safely cache your WordPress HTML assuming your origin sends valid public cache headers for HTML pages. Workers is priced based on the amount of requests that pass through them so it’s best to disable them when unneeded; in my configuration I have Cache Everything enabled and Workers disabled for /wp-includes/* and /wp-content/* as those directories only contain static content and can be cached the same for everyone without using Workers – This saves a large amount of requests hitting Workers unnecessarily and will save money for popular sites.

Pingdom has reported my hourly average homepage load time worldwide as 242ms and 252ms for the last 2 hours whereas it was around 510ms to 530ms prior to enabling Workers with caching.

Categories: SoftwareWordpress

7 Comments

Pairfum London · 6th June 2019 at 12:27 pm

Hi Joseph,

I read your post with great interest.

Do you have an update on this post:
– How well are the Cloudflare workers for you?
– Has your code proven to be solid or did you modify it?
– Where / How did you place your code, in the Cloudflare dashboard or within WordPress functions.php?
– Did you use the special Cloudflare Cache Plugin for workers (https://wordpress.org/plugins/cloudflare-page-cache/)

Would love to hear more about how the workers are ‘working’ for you.

Kind regards,

Pairfum London
https://www.pairfum.com

    Joseph · 6th June 2019 at 2:55 pm

    Hi,

    Thanks for your comment.

    Workers has been working very well for me since I enabled it. I’ve had no issues and everything has been as reliable as expected.

    I made some small modifications to the code as non-200 responses were being cached which meant that temporary issues with the site would be cached and therefore being served to more visitors even after being fixed. The updated code can be found here: https://bit.ly/CfWorker

    All of the code is within the Cloudflare Dashboard and I’m not using any plugins related to Cloudflare. When I first made this code and implemented it, Cloudflare Workers was still a very new feature. Since then, things have changed but I’m not very knowledgable about the new additions to Workers. My code works fine for my situation, but there may now be better ways to do it using the newest features.

Rodney · 14th January 2020 at 6:51 am

Hi Joseph, thanks for sharing this code. I am trying it on my WordPress site (many transactional users) – https://pokerdiy.com and it works well! I just want to clarify when you said you cache everything in a page rule and disable workers on certain rules – did you mean like this: https://i.imgur.com/uvitgGy.png https://i.imgur.com/DkPOXGd.png Thanks!

    Joseph · 14th January 2020 at 11:25 am

    Hi,

    Thanks for your comment.

    It looks like you have `Cache Everything` enabled for all your pages (except wp-json and wp-admin). In my experience, that would break the custom pages for logged in users as everyone would be served pages from the cache.

    In my setup I have `Cache Everything` enabled on wp-content *only* as this is where the WordPress images, scripts, plugins, etc are stored; these can safely be cached for everyone in most cases. Your Workers Routes look the same as mine though.

Chris · 17th July 2020 at 10:46 pm

How would you also add the cookies for woocommerce from this article:

https://support.cloudflare.com/hc/en-us/articles/236166048-Caching-Static-HTML-with-WordPress-WooCommerce

Chris Colotti · 18th July 2020 at 1:14 pm

This is all great info. I have setup to look like this:

https://www.screencast.com/t/ynFmTjudUyR
https://www.screencast.com/t/jQetslaCM

I have found with additional sites running on subdomains with other installations I do need to avoid the *domain/* type configs in routes and other things and use the specific domain URL for things to work best. I am curious if you can add the woocommerce specific mods to the worker that would be great 🙂

Chris · 18th July 2020 at 1:24 pm

Yes same here if I cache everything domain/* and test the contact page as a logged in user then test that page as an incognito user I get the cached page INCLUDING the wordpress admin bar in the cached page. Is that also what you experienced?

Leave a Reply

Your email address will not be published. Required fields are marked *