Easy Asset Cache Busting with Buddy

I’m quite chuffed with this! I’ve been solving this in different ways for a heck of a lot of years, in situations where asset cache busting isn’t part of whatever site building situation Iโ€™m in provides so I need to do it myself. But it always requires a little more technology than I would like, like setting up Gulp or the like.

This is all it is:

Find & Replace. That’s all we are doing here, and Buddy offers that feature.

The Setup

You’ve got a stylesheet on a website. So in the HTML:

<link rel="stylesheet" href="style.css">Code language: HTML, XML (xml)

I’ve got my server/CDN set up to cache the living bejesus out of that file. That’s a no-brainer day-one kind of performance requirement. So if I check the response headers on a file like that, I’ll see something like:

cache-control: public, max-age=31536000Code language: PHP (php)

So when I need to change that file, I need to break that cache, otherwise, users’ browsers that have already been to the site will hang onto their old version of file. That’s worse than a failed deployment, it’s almost like a half/broken deployment because it’s likely HTML has also changed that no longer correctly corresponds to the updated CSS. That’s bad.

A classic easy way to break cache is to add a query parameter:

<link rel="stylesheet" href="style.css<strong>?version=2</strong>">Code language: HTML, XML (xml)

Query parameters are fineยน.

The code we’re looking at? That’s HTML. So the job is:

๏‘‰

Automatically update the version number in the HTML when a new deployment is made that changes the contents of that file.

One possible system: update the query parameters by hand. It’s not that hard of a task, but it’s easy to forget, and the consequences of forgetting, as I mentioned above, suck pretty hard.

Aren’t there lots of ways to do this?

There are (checks notes) a million ways to do this. It’s such a known need that it’s built into all sorts of software. The Rails asset pipeline does this. Bundlers like Rollup, Parcel, Vite, etc will happily do this as well. The assumption in all of these is that it’s processing your HTML and producing a post-processed version of the HTML that is deployed.

Most commonly, I reach for Gulp.
<strong>// For example, I'll set up a watch task for scripts
</strong>gulp.watch(LOCATION_OF_SCRIPTS, doScripts);
<strong>// Then I'll do a bunch of stuff to them when they change.
</strong>function doScripts(done) {
  return gulp.series(
    preprocessJs,
    concatJs,
    minifyJs,
    deleteArtifactJs,
    reload,
    done => {
      cacheBust("./parts/footer-scripts.php", "./parts/");
      done();
    }
  )(done);
}
<strong>// At the end, I'll do a Find & Replace on the cache busting string in the PHP file.
</strong>function cacheBust(src, dest) {
  var cbString = new Date().getTime();
  return gulp
    .src(src)
    .pipe(
      replace(/cache_bust=\d+/g, function() {
        return "cache_bust=" + cbString;
      })
    )
    .pipe(gulp.dest(dest));
}Code language: PHP (php)

To me, assuming that HTML (via PHP or not) will be processed is a fairly big assumption. I have worked on lots of websites that don’t have anything processing the HTML. Or even if there is some kind of HTML processor in place, it doesn’t have an auto-incrementing cache buster automatically in place. Like a basic SSG or whatnot.

So when do we need this?

But here’s my #1 use case: WordPress websites.

A WordPress website produces HTML from PHP templates. There is no processing there other than PHP itselfยฒ. I tend not to have a build process for WordPress sitesยณ.

Using Buddy for the Find & Replace

So here’s my actual line of code in my header.php file on a WordPress site.

<link rel="stylesheet" href="<?php bloginfo('stylesheet_url'); ?>?v=<strong>{{{version}}}</strong>">Code language: HTML, XML (xml)

Note the {{{version}}} in there. That’s just an invented syntax. Just something fairly unique that I’ll have Buddy replace.

Now here’s an example Buddy pipeline. A lot of times, I’ll have one action: just deploy the dang site. But here we’ll add a Find & Replace setup first.

The purge cache at the bottom there is for Cloudflare. It’s just for good measure, but weirdly, not that relevant here, as changing the HTML is enough to break the cache we’re talking about. I do recommend the Cloudflare plugin though and paying for the WordPress-specific optimizations they do though as they are pretty rad.

Within the settings for Find & Replace, we get to tell it what file to run it on, what to look for, and what to replace it with. Miraculously, there are magical variables you have access to (like your ENV vars) including one called $BUDDY_EXECUTION_ID which is a unique ID per deployment, so it’ll work great for cache busting.

In order for this to work, you need to have the Upload files action set to use Pipeline Filesystem and not GitHub. It’ll still pull from GitHub, but apparently, this gives it permission to alter the files before uploading or something. This is crucial and tripped me up for quite a while setting this up. If you don’t do it, it seems like it is all working, but it just doesn’t work, the file uploads unchanged.

You could run the Find & Replace on every deployment, breaking the cache every time, but I’ve added a conditional here to only do it when the actual stylesheet has changed:

Seeing it Run

You can see the Find & Replace execute and succeed in the pipeline logs.

That’ll do it. I can use this on like 3-4 WordPress websites right now, including this one, so I’m happy to have found this simple solution.


  1. There used to be one odd-ball server a million years ago that didn’t respect that and required you to change the name of the file, but let’s not worry about that.
  2. This kinda makes me think that we could do the asset versioning by outputting filemtime($filename) at the end of them, which is the last changed time for the file. I’d just want to make sure good HTML caching is in place then so the server isn’t like hitting the disk for that information on every page view. Probably a can of worms there.
  3. If I work on it alone, I tend to use CodeKit just for a little easy Sass action. It has a cache buster too, but only for HTML that is processed, and probably wouldn’t work for PHP.

๐Ÿค˜

CodePen

I work on CodePen! I'd highly suggest you have a PRO account on CodePen, as it buys you private Pens, media uploads, realtime collaboration, and more.

Get CodePen PRO

3 responses to “Easy Asset Cache Busting with Buddy”

  1. Erik says:

    For those who want a faster solution, they can use the file’s last edited time (timestamp) as the version number as follows:

    <link href="/css/style.min.css?v=” rel=”stylesheet”>

    This will always version the file once for the last edited time.

  2. Matt says:

    We use the filemtime($filename) method you mention in the footnotes for styles and js cache busting in WordPress. It works well to always bust when the asset actually changes. With the linux filesystem caches on the server the read only needs to happen in memory most of the time so performance impact is negligible especially compared to all of the file reads that WordPress already does. Also, with page caching the result of it is cached for most page loads anyway.

  3. You can use a similar approach with GitHub Actions: you can create an action to SSH into the VPS where the site’s been deployed, then use sed to swap out the {{{version}}} magic string with the action’s run number (github.run_number).

Leave a Reply

Your email address will not be published. Required fields are marked *

Back to Top โฌ†๏ธ