I've been looking at the performance of my website and decided to cache some of my assets, like fonts and CSS files. To have them cached for as long as possible, and prevent "cache misses" when no data has changed, I knew I wanted to set a Cache-Control: Max-Age header with a veeeeeeery long timeout.

However, this makes things difficult when I want to change some of the CSS. How do I tell people's browsers that their cache is invalid, and they should load the new styles?

Hashing & Build Tools

The way that many modern frontend build tools do this is by adding a mini hash to the filename. For example, style.css becomes style-c81ac3.css. This small hash isn't cryptographically secure or anything, but it doesn't need to be for the use case. It's so likely it's nearly guaranteed that your changes will result in a different hash (1/16,777,216 chance of failure).

Frontend build tools also typically use templates for the tags (e.g. href="{{ STYLE }}" or swap out references to real files (e.g. href="style.css"). I don't want to do either of those, because they mean maintaining two sets of files: the "templates" and the "built files".

Introducing a modern build tool takes time, and I'm not managing a large client-side rendered JavaScript app — this is a simple personal page. I don't want to introduce Jekyll or Gatsby, since they have far more features and dependencies than I need.

Simple is Best!

In the end, the perfect solution ended up being a bash script. Changing styles while serving the site locally works immediately as expected, as I can configure my browser to ignore the cache. When my changes are done, I run this bash script which does two things for each CSS file I have;

  1. Calculates a hash of the file's contents, and rename that file to use the hash
  2. Search for the old filename in HTML and replace with the new filename

The minihash command I've used to calculate file hashes lives in my ~/.scripts directory on my PATH:

#!/bin/bash
shasum $1 | cut -d' ' -f1 | cut -c -8

It outputs an 8-character hash of a file (trimmed down from a full sha256 hash!).

Important disclaimer: If I were writing this for an important production site I'd have a LOT more safeguards in place, and might not even use any form of this method, likely preferring the template/build approach. Using a regex to replace details in HTML is fine, provided you know exactly what the HTML contains. For my personal site, it's enough to check everything's still working and rollback in git if not!

Here's the final script:

One thig I'm still trying to work out is how to make this bash script portable between MacOS and Linux. Currently I've used the -i argument to run Sed in-place, but I understand that's not in the POSIX standards. Any tips welcome!