Back to All Posts

Your Cache Headers Could Probably be More Aggressive

It's common for modern hosts to cache static assets in a flexible, but not most optimal way. Let's explore why that is and what we can do to push cache performance (for some assets) even further.

We've got it real good when it comes to standing up websites these days (especially static ones). Modern hosts like Vercel and Netlify take care of a lot out of the box, shielding us from the meticulous, complicated stuff.

Caching is one of them. To accommodate the widest range of users, many providers will cache static assets with a Cache-Control header of public, max-age=0, must-revalidate. Translation:

Cache this thing, but immediately let it go stale and ask the origin server if there's a fresh copy to download next time around.

It's a smart default. On each subsequent visit to a page, the browser will always check in with the origin server (or global CDN) for the latest version of the asset, but it'll only actually perform a full download if those assets have changed. If everything's the same, you'll get a 304 Not Modified response back, and the browser's "stale" version will be used. Like this:

non-initial page views with default caching
non-initial page views with default caching

Even though it's performing a distinct GET request to perform that check, if it returns with 304, it'll end up being far smaller & faster than full fetch with a 200. For example, here are a couple of actual requests for a font file from my site. The one that came back with a 304 had a significantly smaller footprint.

screenshot comparing the duration of a 304 and a 200 response

So, while it may sound counterintuitive to immediately let something cached go stale, it's a good balance between performance and flexibility. It'll save some headaches too. You won't end up with someone being served outdated CSS or a previous deployment's JavaScript bundle. Ship as much as you want, and your users will get the latest. If you'd like to dig more into this specific caching strategy, both Netlify and Vercel have some information on the philosophy behind it.

Even so, if you consider yourself a performance scrutineer, it should bother you that you're leaving some speed on the table by sticking with these defaults. Your caching could be a little more aggressive, and in my opinion, it's a no-brainer for particular types of assets.

Some Things Never Change

For a typical content site, most of the assets served via URL aren't dynamic. The same pile of CSS will be used for every visitor. Same story for fonts, individual images, and JavaScript bundles. Certain things just aren't designed to change based on who made the request or when it was performed, and it's technically wasteful to perform an extra HTTP request (albeit a small one) when you're likely to get back 304 anyway.

You can virtually eliminate those unnecessary requests by using a cache header like this instead.

public, max-age=31560000, immutable

Here's what it means:

  • public - the asset can be stored in any cache between (and including) the browser and origin server
  • max-age=31560000 - the cache doesn't have to consider it "stale" until a full year has passed
  • immutable - the browser is explicitly instructed to NOT reach out to origin/CDN just to check if something newer is available (no more revalidation requests)

With that policy in place, after a page has been visited for the first time, each asset is loaded straight from the cache, and the flow ends up looking more like this:

non-initial page views with smarter caching
non-initial page views with smarter caching

As long as the browser's still got a cached copy of the asset (identified by unique URL), it'll be a full year before it ever checks origin again. That makes page performance marginally better, and you can feel a little better about yourself as an performance-minded engineer.

Nothing Lasts Forever

Ok. It's foolishly optimistic to say you'll never need to refresh assets before a year crawls by. You'll update a logo, refresh your site design, or swap out your fonts. It'll inevitably happen.

But serving fresh assets in those scenarios is a problem easy to solve with an age-old cache-busting tactic: fingerprinting. Every time an asset's URL changes (it gets a new "fingerprint"), it'll force the browser and any intermediary cache to treat it as a completely different asset. The URL serves as the cache key, and when it changes, it gets a new identity.

Most frameworks and site builders already do this for you out-of-the-box, by the way, so it's likely that you'll need to do nothing to benefit from it (at least for some asset types). For example, my site's on Astro. On every build, each static asset is given a very unique name:

example of a fingerprinted asset

And for the resources that aren't auto-fingerprinted, you'll get the same benefit using a different file name. For example:

- <img src="./logo.svg" alt="site logo" />
+ <img src="./logo-v2.svg" alt="site logo" />

All of this, by the way, is a good reason not to use overly aggressive caching on your page's HTML itself. The URL of your home page will likely never change, and so it's just not practical to cache it for a year with no revalidation. That's the advantage static assets have over an HTML document. The cache keys of static resources only matter to the HTML that references them. As long as the code's pointed to the correct versions, it doesn't matter what they're named or how frequently they're changed.

How do I do it?

Implementing this highly depends on how you're hosting your site, but before you do, get clear on which types of assets you'd like to cache more aggressively. For my own site, that's every .js, .css, and .woff2 file (my images are already routed through PicPerf, so I'm good there). That list is probably similar for you too.

Customizing Headers on Vercel

If you're on Vercel, you can update your vercel.json file to set specific response headers on the assets you target. I use this:

{
	"headers": [
    	{
			"source": "/(.+\\.js|.+\\.css|.+\\.woff2)",
			"headers": [
				{
					"key": "Cache-Control",
					"value": "public, max-age=31560000, immutable"
				}
			]
		}
	]
}

Be careful writing that pattern, by the way. Vercel follows the path-to-regex syntax – not RegExp. That's caused a moment or two of extreme frustration for me.

Customizing Headers on Netlify

Netlify has its own ways to customize headers using a _headers or netlify.toml file. Here's the same setup using a netlify.toml:

[[headers]]
  for = "/*.(css|js|woff2)"
  [headers.values]
  Cache-Control = "public, max-age=31536000, immutable"

Using Cloudflare

If you're on a provider that doesn't permit customizing response headers so easily, you're not out of luck. You can set up a Cloudflare account to act as a reverse proxy and set the response headers using a modification rule:

Or, if you'd like to do it in a more interesting way, intercept the request and modify the response with a Cloudflare Worker. Use something like itty-router and it'll amount to less than 30 lines of code:

import { Router, IRequest } from "itty-router";

const router = Router();

router.get("/*.(css|js|woff2)", async (request: IRequest) => {
  const response = await fetch(request);

	return new Response(response.body, {
		status: response.status,
		statusText: response.statusText,
		headers: {
			...response.headers,
			"cache-control": "public, max-age=31560000, immutable",
		},
	});
});

export default {
	async fetch(
		request: Request,
		env: {},
        context: ExecutionContext
	): Promise<Response> {
	context.passThroughOnException();

	return router.handle(request, env, context).then((response) => response);
	},
};

This, by the way, is the same approach used to cache every image optimized by PicPerf.dev. I love it.

Spend more time in the Network Tab.

The only reason I started thinking about this was because I was curious about the network activity behind any given page load on my own site. It struck me as too much, considering my site's fairly simple and doesn't have any ads or other network-chatty things. It was fun, I learned a lot, and my site came out a little quicker in the process.

Dive into those tools yourself, and maybe you'll emerge with a similar tip of your own. If you do, I'd love to hear it.

哆哆女性网映客直播网页林长民sm小说在线阅读测试缘分指数班级起名适合用在公司起名的字孩子起名 五行属对百货商店起名姓氏三才五格起名姓名学测试西餐厅加盟电脑无法复制粘贴绝句四首起小名男孩姓名学起名字典建筑安装工程施工图集易学取名起名大全二类本科院校排名给牧羊犬起啥名字好听巡回检察组第12集幼犬起名大全瑜珈馆起名柯哀的高中事件簿纲手本子马姓宝宝起名大全大全软装公司起名艺术维修公司起名字大全免费韩国拌饭五行缺金男孩起名宝典张姓属猪宝宝起名手机测网速淀粉肠小王子日销售额涨超10倍罗斯否认插足凯特王妃婚姻不负春光新的一天从800个哈欠开始有个姐真把千机伞做出来了国产伟哥去年销售近13亿充个话费竟沦为间接洗钱工具重庆警方辟谣“男子杀人焚尸”男子给前妻转账 现任妻子起诉要回春分繁花正当时呼北高速交通事故已致14人死亡杨洋拄拐现身医院月嫂回应掌掴婴儿是在赶虫子男孩疑遭霸凌 家长讨说法被踢出群因自嘲式简历走红的教授更新简介网友建议重庆地铁不准乘客携带菜筐清明节放假3天调休1天郑州一火锅店爆改成麻辣烫店19岁小伙救下5人后溺亡 多方发声两大学生合买彩票中奖一人不认账张家界的山上“长”满了韩国人?单亲妈妈陷入热恋 14岁儿子报警#春分立蛋大挑战#青海通报栏杆断裂小学生跌落住进ICU代拍被何赛飞拿着魔杖追着打315晚会后胖东来又人满为患了当地回应沈阳致3死车祸车主疑毒驾武汉大学樱花即将进入盛花期张立群任西安交通大学校长为江西彩礼“减负”的“试婚人”网友洛杉矶偶遇贾玲倪萍分享减重40斤方法男孩8年未见母亲被告知被遗忘小米汽车超级工厂正式揭幕周杰伦一审败诉网易特朗普谈“凯特王妃P图照”考生莫言也上北大硕士复试名单了妈妈回应孩子在校撞护栏坠楼恒大被罚41.75亿到底怎么缴男子持台球杆殴打2名女店员被抓校方回应护栏损坏小学生课间坠楼外国人感慨凌晨的中国很安全火箭最近9战8胜1负王树国3次鞠躬告别西交大师生房客欠租失踪 房东直发愁萧美琴窜访捷克 外交部回应山西省委原副书记商黎光被逮捕阿根廷将发行1万与2万面值的纸币英国王室又一合照被质疑P图男子被猫抓伤后确诊“猫抓病”

哆哆女性网 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化


my face

Alex MacArthur is a software engineer working for Dave Ramsey in Nashville-ish, TN.
Soli Deo gloria.

Get irregular emails about new posts or projects.

No spam. Unsubscribe whenever.
Leave a Free Comment

0 comments

Get irregular emails about new posts or projects.

No spam. Unsubscribe whenever.

哆哆女性网映客直播网页林长民sm小说在线阅读测试缘分指数班级起名适合用在公司起名的字孩子起名 五行属对百货商店起名姓氏三才五格起名姓名学测试西餐厅加盟电脑无法复制粘贴绝句四首起小名男孩姓名学起名字典建筑安装工程施工图集易学取名起名大全二类本科院校排名给牧羊犬起啥名字好听巡回检察组第12集幼犬起名大全瑜珈馆起名柯哀的高中事件簿纲手本子马姓宝宝起名大全大全软装公司起名艺术维修公司起名字大全免费韩国拌饭五行缺金男孩起名宝典张姓属猪宝宝起名手机测网速淀粉肠小王子日销售额涨超10倍罗斯否认插足凯特王妃婚姻不负春光新的一天从800个哈欠开始有个姐真把千机伞做出来了国产伟哥去年销售近13亿充个话费竟沦为间接洗钱工具重庆警方辟谣“男子杀人焚尸”男子给前妻转账 现任妻子起诉要回春分繁花正当时呼北高速交通事故已致14人死亡杨洋拄拐现身医院月嫂回应掌掴婴儿是在赶虫子男孩疑遭霸凌 家长讨说法被踢出群因自嘲式简历走红的教授更新简介网友建议重庆地铁不准乘客携带菜筐清明节放假3天调休1天郑州一火锅店爆改成麻辣烫店19岁小伙救下5人后溺亡 多方发声两大学生合买彩票中奖一人不认账张家界的山上“长”满了韩国人?单亲妈妈陷入热恋 14岁儿子报警#春分立蛋大挑战#青海通报栏杆断裂小学生跌落住进ICU代拍被何赛飞拿着魔杖追着打315晚会后胖东来又人满为患了当地回应沈阳致3死车祸车主疑毒驾武汉大学樱花即将进入盛花期张立群任西安交通大学校长为江西彩礼“减负”的“试婚人”网友洛杉矶偶遇贾玲倪萍分享减重40斤方法男孩8年未见母亲被告知被遗忘小米汽车超级工厂正式揭幕周杰伦一审败诉网易特朗普谈“凯特王妃P图照”考生莫言也上北大硕士复试名单了妈妈回应孩子在校撞护栏坠楼恒大被罚41.75亿到底怎么缴男子持台球杆殴打2名女店员被抓校方回应护栏损坏小学生课间坠楼外国人感慨凌晨的中国很安全火箭最近9战8胜1负王树国3次鞠躬告别西交大师生房客欠租失踪 房东直发愁萧美琴窜访捷克 外交部回应山西省委原副书记商黎光被逮捕阿根廷将发行1万与2万面值的纸币英国王室又一合照被质疑P图男子被猫抓伤后确诊“猫抓病”

哆哆女性网 XML地图 TXT地图 虚拟主机 SEO 网站制作 网站优化