Speeding Up Your Website Using Cloudflare Cache
Written by Lilou Artz on .
#performance, #caching, #cloudflare
Performance is critical for websites to rank in Google search results. Pillser implements a number of techniques to load and render pages quickly. However, nothing beats caching. In this post, I will share my experience with Cloudflare cache.
Cloudflare Cache
I chose Cloudflare Cache because I am already using Cloudflare for other things.
To use Cloudflare cache, I needed to:
- Enable Tiered Cache
- Enable Cache Reserve
- Add Cache Rules
Tiered Cache and Cache Reserve are not strictly necessary, but they enable more reliable and faster cache hits.
When you enable Cache Reserve, you are able to cache gigabytes of data. Meanwhile, Tiered Cache reduces the amount of servers that Cloudflare needs to hop through to serve your website, which improves performance, e.g. I saw cached response times go from 100ms to under 10ms when I enabled Tiered Cache.
Shoutout to Lighthouse Metrics as their service has been a great help in understanding how Pillser is performing on the web accross the globe.
Finally, you need to add Cache Rules to define which pages should be cached. For example, I only want to cache pages that are accessed by non-authenticated users (identified by the presence of a user_account
cookie), and I only want to cache pages matching a specific URL pattern. Here is a rule that does just that:
(
not http.cookie contains "user_account" and (
http.request.uri.path eq "/" or
starts_with(http.request.uri.path, "/supplements") or
starts_with(http.request.uri.path, "/probiotics") or
starts_with(http.request.uri.path, "/vitamins") or
starts_with(http.request.uri.path, "/minerals") or
starts_with(http.request.uri.path, "/brands")
)
)
I love that Cloudflare cache is so flexible. Their rules language is very powerful.
Cache by Device Type
You can enable options like Cache by device type if you are serving different content to different devices. Example: Pillser will render a different number of supplements per page depending on whether the user is on a mobile device or a desktop.
Once enabled, Cloudflare sends a CF-Device-Type
HTTP header to your origin with a value of either mobile
, tablet
, or desktop
for every request to specify the visitor's device type.
Utilize Strong ETags
The ETag HTTP response header is an identifier for a specific version of a resource.
Enable Respect strong ETags to ensure that the cache is invalidated when the content changes.
For this to function, you need to add a ETag
header to the response. Fastify ecosystem has a plugin to automatically generate strong ETags.
Serve Stale Content While Revalidating (Not Working as Expected)
This is the only thing that I was not able to figure out.
My ideal behavior would be to cache products for a short period of time (e.g., 1 hour) and then serve stale content while revalidating.
I have therefore configured Edge TTL
to Ignore cache-control header and use this TTL
and set the TTL to 1 hour. This ensures that the cache becomes stale after 1 hour.
I have then left Do not serve stale content while updating
disabled. This is supposed to make Cloudflare serve stale content while revalidating, but it does not seem to work.
I am still occasionally seeing content being served directly from the origin with cf-cache-status
set to MISS
. I would expect this to not happen, as the revalidation should happen in the background while the cache is being served. If you happen to know how to fix this, please let me know.
Lacking Features: Max Age for Stale Content
Another thing that I noticed is that Cloudflare will sometimes expire cached content based on Cache-Control
headers. However, in the example of wanting to serve stale content while revalidating, I would expect that there would be a setting that allows me to explicitly say how long the content should be cached for regardless of the Cache-Control
header, i.e., I would want to set max-age to several days, but require that Cloudflare revalidates the content every hour.
Effectively, I want to force Cloudflare to retain the cache beyond the TTL.
Purging Cache
Last but not least, I needed a way to purge the cache. Cloudflare provides several ways to purge the cache. However, I found that the API approach is the easiest to use:
import { config } from '#app/config.server';
import Cloudflare from 'cloudflare';
const cloudflare = new Cloudflare({
apiEmail: config.CLOUDFLARE_API_EMAIL,
apiKey: config.CLOUDFLARE_API_KEY,
});
const response = await cloudflare.cache.purge({
files: ['https://pillser.com/'],
zone_id: config.CLOUDFLARE_ZONE_ID,
});
This allows me to automate the purging of individual product cache, e.g. when a product is updated.
Results
I ran latency tests from several locations and captured the slowest response time for each URL. The results are below:
URL | Country | Origin Response Time | Cached Response Time |
---|---|---|---|
https://pillser.com/vitamins/vitamin-b1 | us-west1 | 240ms | 16ms |
https://pillser.com/vitamins/vitamin-b1 | europe-west3 | 320ms | 10ms |
https://pillser.com/vitamins/vitamin-b1 | australia-southeast1 | 362ms | 16ms |
https://pillser.com/supplements/vitamin-b1-3254 | us-west1 | 280ms | 10ms |
https://pillser.com/supplements/vitamin-b1-3254 | europe-west3 | 340ms | 12ms |
https://pillser.com/supplements/vitamin-b1-3254 | australia-southeast1 | 362ms | 14ms |
The results are consistent across multiple regions. It is clear that Cloudflare cache hugely improves the performance of the website, especially for users further away from the origin (US).