Enable CDN (Cloudflare) caching for your Netlify site
This post goes through how to reduce the amount of static assets served through Netlify by leveraging a CDN (Content Delivery Network) like Cloudflare. This will reduce your Netlify bandwidth usage for cacheable assets.
It will also mean that you’re following industry-accepted best practices around setting a time for which your assets should be cached by receiving browsers.
In principle, it won’t significantly improve the speed at which your site loads since Netlify’s built-in CDN does a good job of that anyways. It will mean that you’re leveraging your CDN’s capacity to deliver assets quickly as opposed to Netlify’s.
Table of Contents
What is Netlify’s default caching behaviour?
The default Netlify cache-control header, are
"max-age=0, must-revalidate, public" which means all content should be cached but also re-validated. The rationale is that “This favors you as a content creator — you can change any of your content in an instant.” - Better Living Through Caching.
This means that web performance tests will give you lower marks for not having a reasonable caching length for static assets.
Netlify suggests not to use a CDN in front of it (see Why You Don’t Need Cloudflare with Netlify). The core points of this article is that Netlify provides the following which a CDN also tend to help with:
- SSL: Netlify creates certificates so that your site is always served over HTTPS.
- CDN: Netlify’s CDN is very clever when used along its managed DNS service, with smart load balancing/routing and DDoS prevention baked in.
- DNS: Setting up (custom) domains with Netlify’s DNS is fast and simple.
These 3 services are services also provided by Cloudflare.
Setting caching headers on Netlify
The Netlify documentation for Custom headers doesn’t have a caching example. It also suggests there are 2 ways to add custom headers: through a
_headers file and as part of a
[[headers]] section of your
Here’s a sample
netlify.toml setup for codewithhugo.com, ignore the
[build] section or see the The netlify.toml File article in Netlify’s knowledge base:
[build] command = "./scripts/build.sh" publish = "public" functions = "lambda" [[headers]] for = "/img/*" [headers.values] Cache-Control = "public, s-max-age=604800" [[headers]] for = "/*.css" [headers.values] Cache-Control = "public, s-max-age=604800" [[headers]] for = "/*.js" [headers.values] Cache-Control = "public, s-max-age=604800"
/img/* Cache-Control: public, s-max-age=604800 /*.css Cache-Control: public, s-max-age=604800 /*.js Cache-Control: public, s-max-age=604800
Cache-Control we’re setting is
public, s-max-age=604800. We’re using
s-max-age which according to the MDN Cache-Control article is:
Takes precedence over
Expiresheader, but it only applies to shared caches (e.g., proxies) and is ignored by a private cache.
This means we’re using custom headers on Netlify to tell Cloudflare (our CDN) how long to cache assets. We’ll then control how long browsers cache using CDN settings.
Cache rules breakdown
From Cloudflare statistics I know that images + CSS account for more than 60% of my traffic.
Serving images out of Netlify doesn’t seem too useful since they don’t change often. They’re also not actually part of the core codewithhugo.com offering, they’re “nice-to-have” as opposed to the “must-have” text + code content. That’s where the first rule comes in:
[[headers]] for = "/img/*" [headers.values] Cache-Control = "public, s-max-age=604800"
/img/* Cache-Control = "public, s-max-age=604800"
The CSS I generate through Hugo has a cache-busting hash baked in. So old versions and new versions will be fetched and cached separately. It’s therefore safe to aggressively cache across the CDN and in browsers.
netlify.toml headers format:
[[headers]] for = "/*.css" [headers.values] Cache-Control = "public, s-max-age=604800"
/*.css Cache-Control: public, s-max-age=604800
netlify.toml headers format:
[[headers]] for = "/*.js" [headers.values] Cache-Control = "public, s-max-age=604800"
/*.js Cache-Control: public, s-max-age=604800
My cached requests percentage has gone from 5% to over 65%. Since Cloudflare is serving most of my heaviest content (images), it’s resulted in an 80% Cloudflare → Netlify bandwidth usage reduction.
Caching assets with @Cloudflare
Left ~5%, default Netlify headers ()
Right 50%+, with Cache-Control: public, s-max-age=604800
ie. Allow a CDN in front of this site to cache assets 1 week
— Hugo Di Francesco (@hugo__df) March 20, 2019
Get The Jest Handbook (100 pages)