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.
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.
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
[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.
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