Full Page Caching in Umbraco with Cloudflare

Some of you may remember a post a long time ago (okay, it’s only about 4 posts back so it’s not that hard to scroll back) where I extolled the virtues of Cloudflare. At the risk of sounding like a fanboy, as there are other solutions available, this is a bit of a followup to that. Towards the latter part of this year I started rolling it out to some more complex Umbraco sites – I say complex in the sense of they were being regularly updated, and had forms and some dynamic parts to them, rather than just being simpler flat HTML and image based sites. When combining this with a setup where caching the entire output HTML is beneficial, it does introduce some additional design considerations to the site.

I’ve got this down to a fairly standardised three pronged approach on my own projects now:-
1) Leverage the built in WebAPI for retrieving regularly changing data and for posting back form data. I sometimes feel this is one of the most underappreciated bit of Umbraco’s functionality, probably not helped by how well buried it tends to be on any Umbraco documentation and their videos. But if a controller is set up that inherits from UmbracoApiController, it’s automatically wired up on the url /umbraco/api/YourController. This makes it very easy to then just exclude the /umbraco* path from Cloudflare using a single page rule, something you should probably already be doing anyway to avoid issues with the admin section. Just remember to still implement your own small bits of data caching on individual methods if you expect it to be performing something particularly heavy. You can then either pull this data into pages or post back to it using JQuery (other Javascript frameworks are available depending on the day of the week). As the Javascript is still always executed client-side, it’s completely unaffected by the full page caching.
2) Avoid processing things in the pages if the resulting output is not meant to be the same for everyone, which also means if you have logged in personalisation, this whole approach might not be ideal for the site. A common example being tracking or randomising something on the page, as if not careful only the first person gets tracked, and everyone will see the exact same ‘random’ version. If something on page does need to be user specific, if this is a small area it can be achieved by wiring up a call to a method from an api class too as above. In performance terms, it’s worth mentioning these first two don’t just apply to using it with CF, but can be used to enable any form of full page caching. In one instance, I’ve used it to allow me to set up controllers behind every Umbraco Doctype and then just leveraged the inbuilt MVC controller OutputCache attributes to avoid pretty much any server load. That’s all native .net and doesn’t even touch an external service.
3) Hook on to the Umbraco Publish event in order to talk to the Cloudflare API and clear a page on publish. Depending on how time critical seeing a change on the page is, this might not even be necessary. However their API is quite trivial to connect to for doing this – the hardest parts are probably getting used to the fact you have to pass over the complete URL with domain name, which you might not always get back from Umbraco depending on the setup in place, and realising the Zone ID is listed on your Cloudflare Dashboard so you don’t even need to make an API call to find it as their docs imply.

Something as basic as:-

    string cfZoneId = "get_this_from_your_cf_dashboard";
    string cfEmail = "emailyouloginwith@example.com";
    string cfKey = "get_this_from_your_cf_dashboard";
    List<string> urlsToDelete = new List() { "http://www.mydomain.com/pageurl" };
    HttpClient client = new HttpClient();
    using (HttpRequestMessage cfRequest = new HttpRequestMessage(HttpMethod.Delete, String.Format("https://api.cloudflare.com/client/v4/zones/{0}/purge_cache", cfZoneId)))
    {
        var urlsObject = new {files = purgeUrls};
        var urlsJson = JsonConvert.SerializeObject(urlsObject);
        cfRequest.Headers.Add("X-Auth-Email", cfEmail);
        cfRequest.Headers.Add("X-Auth-Key", cfKey);
        cfRequest.Content = new StringContent(urlsJson, Encoding.UTF8, "application/json");
        var response = await client.SendAsync(cfRequest);
    }

Should even do it, although obviously this can be improved, made to work better asynchronously, fail better with logging, use whatever Microsoft have decided is the new class for making web requests this week etc.

Just a few ideas on some simple ways you can improve performance. Once you start playing with this, it actually becomes quite hard to put down though, or maybe that’s just me!

Useful References:-
* https://our.umbraco.org/documentation/reference/routing/webapi/ – Umbraco WebAPI
* https://our.umbraco.org/documentation/reference/events/contentservice-events – Hooking on to Publishing Event
* https://api.cloudflare.com/ – Cloudflare API docs