You should for sure be setting far-out cache headers on your assets like CSS and JavaScript (and images and fonts and whatever else). That tells the browser “hang on to this file basically forever.” That way, when navigating from page to page on a site — or revisiting it, or refreshing the page — the browser doesn’t have to download it again which produces way faster page loads. It’s massively important for web performance, so do it!
But how do you force the browser to get a fresh version of the file? Well, there are a bunch of ways. Check out that blog post for more. But here’s one that I’ve used just recently that I wanted to document.
The trick is to change the query string
There was an era where the prevailing wisdom was that changing the query string wasn’t enough, but even then it, the reasons it wouldn’t work were pretty much edge cases. These days, changing the query string is fine (assuming you don’t change the default behavior, services like Cloudflare let you do it).
So, one day you ship it like this in your HTML:
<link rel="stylesheet" href="style.css?v=1" />
Then you change that query string to break the cache when you need to:
<link rel="stylesheet" href="style.css?v=2" />
The HTML, by the way, is either not cached or cached for a much shorter amount of time, so changes to HTML will be seen.
I sometimes do it by hand
For many years, I busted cache on this very site by setting a PHP variable and using it to break assets, like…
<?php $ver = 1.0; ?>
<link rel="stylesheet" href="style.css?v=<?php echo $ver; ?>" />
<link rel="stylesheet" href="some-other-asset.css?v=<?php echo $ver; ?>" />
Hey that works, but it was me hand-manipulating that variable. I would sometimes forget to do it, and even if I did remember, I sort of resented having to do it.
Automating version busting with Gulp
I’m using a Gulp-powered build process at the moment on this site, which does all the classic stuff: Sass, Babel, file concatenation, live reloading…
It occurred to me I might as well have Gulp do the query-string changing whenever changes are made to CSS or JavaScript. JavaScript has a .replace()
method, and that’s available in Node/Gulp easily with the gulp-replace plugin.
I make a task. When I call it, it looks in my header filer for the string cache_bust=
plus some value, and replaces it with a new randomized string based on the date and time.
gulp.task("cache-bust-css", function() {
var cbString = new Date().getTime();
return gulp
.src(["header.php"])
.pipe(
replace(/cache_bust=\d+/g, function() {
return "cache_bust=" + cbString;
})
)
.pipe(gulp.dest("."));
});
I do the same thing in a separate task when JavaScript files are editing and compiled.
It’s still a little dumb
Notice that I’m changing the query string on all the CSS assets every time any of them changes. That’s not as efficient as it could be. I should probably be changing the query string only on the changed files.
I’ll get to it eventually. Sometimes you just gotta baby-step your way to better solutions over time.
This is just one way! There are other Gulp plugins just for this. Other build systems have different approaches. This approached happened to work well for me and my exact needs at the time. Feel free to share your strategy!
Instead of a version or a datetime, use a hash of the file to bust the cache. That way it only changes when the file changes.
Or, you could use actual cache headers, or E-Tag headers, which is exactly what they are for. Doing this in the URL ties the HTML and CSS so that now you need to make changes to both in order to ensure users get the correct version. Some CDNs don’t treat the query string as a unique URL.
Changing the query string is not how to handle caching.
I’ve literally been doing it for a decade.
Doing something a particular way for a long time doesn’t make it the correct.
Have a look at ETags and what they can do: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/ETag
Probably worth bearing in mind that this works from the perspective of the file, not whatever is requesting it. Don’t forget that not everything requesting your resources will be under your control, so ETags are a way to serve the correct version and do cache invalidation correctly for all scenarios.
@Ashley Sheridan Please read https://css-tricks.com/strategies-for-cache-busting-css/#article-header-id-4 and https://stackoverflow.com/a/47535993/616067 about ETags and the problems that come with it.
Ashley,
Using ETags is part of a caching strategy, in my opinion, but bear in mind that in order to identify outdated ETags, you still need a trip to the server, only to get a 304 – Not Modified. If you can guarantee that the resource is not outdated during build, I think it’s better to do it then. File hashing and referencing (e.g. “app-1ed9ab.js”) during a build is a very good way to do that, or even using a querystring when you don’t have a more advanced build pipeline available. This ensures that you can use Far-Future Expiration caching, where the browser does not even require a check with the server.
Hey Chris,
Look into the excellent gulp-rev and gulp-rev-replace packages to do this for you. We’ve been using them for years in my organization, and they are rock-solid for FFE setups with cache-busting.
Cheers!
Simon
Last modified (date)… That’s all I have to say. ;-)
Honestly wishing that mobile Chrome would actually refresh the CSS when you try an explicit refresh (like on desktop browsers), but it seems to treat that as just a reload? (and it’ll happily hold onto the old stylesheet for over a week unless you resort to stuff like this) Only way to force a refresh seems to be to completely drop the entire cache from the settings? :/
It’s pretty annoying when you’re serving up static HTML files (because there’s nothing else to be done and you want a speedy site) as that means having to update everything just for a minor stylesheet change. Either that, or only do changes that won’t be deal breaking if they aren’t applied yet.
In my gulp and weppack setup https://github.com/jmartsch/acegulp4 in the file https://github.com/jmartsch/acegulp4/blob/master/gulpfile.babel.js/tasks/rev.js I use gulp-rev-all and a rev-manifest to generate hashed filenames based on the contents of a file. Then I use a PHP function to get the actual version of an image, CSS or JS file to load.
This is cool, I actually do this using
filemtime
in PHP and using that timestamp, only the assets that were modified will get their query string changed! :)I’m sure there’s a far more elegant way to do it, but on a simple site with only a main stylesheet, this works in WordPress:
I’ve seen some comments regarding timestamps that folks have used. While that works for your situations, for others reading this, bear in mind that that may not work in all cases. For instance, we upload our assets to Microsoft Azure Blob Storage, which has had a tendency to not honor or manipulate time stamps when the asset gets created. You could find yourself in a situation where a non-changed asset gets invalidated in the cache and subsequently reacquired. That’s where hashing the contents of the file, while slightly more work perhaps, is advantageous.
Hello!
I truly like to do this:
<link rel="stylesheet" href="style.css?v=” />
@Eduard. Well you can do this, but this only shifts the problem to another position. You have to remember to update the v parameter by hand, as Chris wrote: ” I would sometimes forget to do it, and even if I did remember, I sort of resented having to do it.”
You could also use the file modification date as others wrote, which could cause a reload even if the file contents have not changed and the file modification date is altered.
IMHO the cache busting via hashed filenames and an allocation map (namely rev-manifest) is the best solution.