Object Versioning / Cache Invalidation

More from this category




Multiple cache busting setups below this article

Object Versioning Instead of Cache Invalidation

Object Versioning Instead of Cache Invalidation

One of the most common questions we get in WP Offload Media support is something along the lines of …

Why is there a bunch of random numbers in my URLs, and how can I remove them?

The short answer is because WP Offload Media’s Object Versioning setting is turned on by default.

However, this only invites the questions; “Why?”, “What if I don’t want those extra numbers in my URLs?”, and “Is it safe to turn off Object Versioning?”

Let’s answer those questions.

What is Object Versioning?

When WP Offload Media offloads a Media Library item to a storage provider’s bucket, it constructs a “Key” (a.k.a. path) for the files using various settings.

The obvious settings are “Path” and “Year/Month“.

Offload Media Storage Settings
Offload Media Storage Settings

They might give us something like “wp-content/uploads/” and “2020/01/” respectively, meaning a file called “puppies.jpg” uploaded to the Media Library on January 29th 2020 might end up with a key in the bucket like so:

wp-content/uploads/2020/01/puppies.jpg

However, if Object Versioning is turned on, that key might instead be something like:

wp-content/uploads/2020/01/29173059/puppies.jpg

That extra “29173059” segment in the file’s key comes from WP Offload Media’s Object Versioning setting. It’s simply a timestamp, in this case we can tell that puppies.jpg was offloaded to the bucket on the 29th day (of Jan), at 17:30 and 59 seconds.

If that file was then served via a CDN such as Amazon CloudFront using a custom domain (CNAME) of “cdn.example.com”, the final URL seen on a page for that image might be:

https://cdn.example.com/wp-content/uploads/2020/01/29173059/puppies.jpg

If that puppies.jpg was then removed from the bucket, and re-offloaded a few days later, it could end up with a different key in the bucket, such as:

wp-content/uploads/2020/01/04132435/puppies.jpg

Now the Object Version is “04132435”, it has changed because WP Offload Media generates a new version when a file is offloaded to ensure that the object’s key is unique for that file’s re-offload.

Now the image’s URL is:

https://cdn.example.com/wp-content/uploads/2020/01/04132435/puppies.jpg

Because WP Offload Media takes care of rewriting the local URLs for offloaded Media Library items when a site visitor views a page, nothing breaks, the visitor sees the updated puppies.jpg image, unaware that it has been moved to a different path in the bucket.

Why Use Object Versioning?

Why do we do this by default? Why muck about with the URL for an offloaded Media Library item? Why can’t our updated puppies.jpg file be offloaded to the same path every time?

Content Delivery Networks (CDNs), that’s why!

When you use a CDN such as Amazon CloudFront, Cloudflare or KeyCDN, they cache the files they serve for a set period of time, usually somewhere between a day to a year, and don’t refresh the cache until that time is up.

Let’s say you upload a photo of a cute puppy with key wp-content/uploads/2020/01/puppies.jpg, publish, and people visit your post. But shortly afterward you find an even cuter puppy photo. You upload the new one using the Enable Media Replace plugin so the image keeps the same key wp-content/uploads/2020/01/puppies.jpg. Since the CDN has already cached the first less cute puppy, it will continue to serve that one. Your site visitors may not see the cuter puppy for a year!

If you have Object Versioning turned on and replace that puppies.jpg photo, WP Offload Media generates a new key for the file to be offloaded, starts using that new path in all content, and removes the old less cute puppies photo from the bucket.

Because WP Offload Media’s Object Versioning generates a new key for an offloaded file when it is re-offloaded, the CDN sees it as a new file and pulls it into its edge server caches.

The method of avoiding cache invalidation is recommended by Amazon CloudFront:

When you update existing files in a CloudFront distribution, we recommend that you include some sort of version identifier either in your file names or in your directory names to give yourself better control over your content. This identifier might be a date-time stamp, a sequential number, or some other method of distinguishing two versions of the same object.

Why Would We Need to Update the Offloaded File Anyway?

There are all kinds of reasons that you might need to update an offloaded Media Library item and therefore need those CDN edge servers to notice the new file and start serving it.

Maybe you’ve just updated to a new theme that has different image thumbnail size requirements.

You’ve noticed a whole bunch of images were uploaded that hadn’t been optimized for web delivery.

Perhaps Bob has finally got around to providing a more up to date head-shot for the staff index page that you’re eager to use in place of the old creepy one.

That embedded PDF report needs replacing to fix a problem with its formatting.

There’s always even cuter puppies! 😂

Either way, after you’ve used something like the Regenerate Thumbnails, EWWW Image Optimizer or Enable Media Replace plugins to fix up your media, you need your CDN to re-cache and serve the newly offloaded files.

Is it Safe to Turn Off Object Versioning?

No.

Well, if you’re not using a CDN then I guess you’re ok. But honestly, you really should be using a CDN.

And then again, what about the browser cache? If you have repeat visitors and update an important image that you want them to see changes to when returning, Object Versioning will help with that. WordPress has a built in `?ver=x.y.z” param that it adds to deliverables such as CSS and JS files, but that doesn’t extend to media in themes and plugins, let alone in content.

If you offload your media and use Object Versioning you get the ability to emergency update your site visitor’s extremely distributed extended and super localized content delivery network edge location that is their browser!

Is There an Alternative?

No.

Seriously? No alternative?

Not really.

You could use a scheme such as CloudFront suggests for Updating Existing Content Using the Same File Names, and simply set a pretty short Maximum Time To Live in the CDN distribution. The problem is though, objects are given a Cache-Control header equal to one year, and CloudFront will respect that header, believing that the object at that path is good for a year before it should be re-fetched.

You could adjust that Cache-Control header to something much shorter via WP Offload Media’s “as3cf_object_meta” filter by overriding the $args['CacheControl'] value. However, using S3/CloudFront as our example, because each CloudFront edge server situated in different regions now needs to re-fetch any requested object more often, you’ll incur much higher S3 requests costs, even though there are no data transfer costs to CloudFront.

You also no longer have control of when the object is re-fetched, it’s purely based on the interval that CloudFront performs its checks, whereas with Object Versioning the new object has to be re-cached and served on first request by each CDN edge server.

You could I suppose rig up some convoluted mechanism that uses the as3cf_post_upload_attachment filter to trigger an Invalidation of the files path in the CDN distribution. But that is relatively slow, and could get expensive fast seeing as CloudFront gives you just 1,000 free invalidations per month, and then charges $0.005 per invalidation path requested.

You can of course manually invalidate any path in a CDN distribution via the provider’s console. In our doc for how to fix CORS issues with web fonts we show how to invalidate an entire Amazon CloudFront distribution with a path of “/“, but you could invalidate just “/wp-content/uploads/2018/” if you’ve just re-offloaded some Media Library items from 2018.

Besides, it’s pretty clear that CloudFront recommends Object Versioning over Invalidation

If you’ll want to update your files frequently, we recommend that you primarily use file versioning

Summary

As you can see, by far the easiest and cheapest method of making sure your latest and greatest files are being served as quickly as possible via a CDN is with WP Offload Media’s Object Versioning setting turned on. This is why it is turned on by default and we very much recommend you use it.


Multiple stle files

WordPress Theme Stylesheet Auto-Cachebusting

WordPress Theme Stylesheet Auto-Cachebusting

If you’re like me and you set up server-side caching, CloudFlare (or any other proxy cache), and/or long Expires headers on your theme stylesheets, you know the hassles that go into invalidating those caches and forcing browsers to load an updated version.

One method to force browsers to pull the new version is to add/change the ver parameter in the query string (WordPress’ wp_register_script, wp_register_style, wp_enqueue_script, and wp_enqueue_style have a built-in way of doing this).

Query Strings

Defining a Constant

Here’s how I used to do it: define a constant and then manually update that, as well as the Version header in the style.css file.

[code style=”php”]
<?php

define( ‘MY_THEME_VERSION’, ‘1.4’ );

/**
* Enqueue stylesheet
*
* Uses a constant that has to be manually updated
*/
function my_theme_enqueue_assets() {
wp_enqueue_style( ‘stylesheet’, get_stylesheet_uri(), array(), MY_THEME_VERSION );
// stylesheet URL: https://mydomain.com/wp-content/themes/my-theme/style.css?ver=1.4
}
add_action( ‘wp_enqueue_scripts’, ‘my_theme_enqueue_assets’ );

[/code]

The main drawback is that I had to manually update the version number in two places: functions.php and style.css.

File Modification Time

Another method I’ve used at times is to add the style.css file modification time as the query string.

[code style=”php”]
<?php

/**
* Enqueue stylesheet
*
* Uses the modification timestamp of the style.css file
*/
function my_theme_enqueue_assets() {
wp_enqueue_style( ‘stylesheet’, get_stylesheet_uri(), array(), filemtime( get_stylesheet_directory() . ‘/style.css’ ) );
// stylesheet URL: https://mydomain.com/wp-content/themes/my-theme/style.css?ver=1518212243
}
add_action( ‘wp_enqueue_scripts’, ‘my_theme_enqueue_assets’ );

[/code]

This has the advantage that the cache will be invalidated every time the stylesheet file changes. For some reason, I really never cared for it that much, though. It does add a tiny bit of performance hit, as the filemtime has to go get the file and figure out the timestamp.

WordPress Theme Version

I just read about this today and I think it’s going to be my go-to method in the future. It uses one of WordPress’ built-in theme functions to get the version number from the stylesheet.

[code style=”php”]

<?php

/**
* Auto-calculate and define theme version constant for use in multiple stylesheets/scripts
*/
define( ‘MY_THEME_VERSION’, wp_get_theme()->get( ‘Version’ ) );

/**
* Enqueue stylesheet
*
* Uses the Version header from style.css
*/
function my_theme_enqueue_assets() {
wp_enqueue_style( ‘stylesheet’, get_stylesheet_uri(), array(), MY_THEME_VERSION );
// stylesheet URL: https://mydomain.com/wp-content/themes/my-theme/style.css?ver=1.4
}
add_action( ‘wp_enqueue_scripts’, ‘my_theme_enqueue_assets’ );

[/code]

[code style=”php”]

/**
* Theme Name: Twenty Seventeen
* Theme URI: https://wordpress.org/themes/twentyseventeen/
* Author: the WordPress team
* Author URI: https://wordpress.org/
* Description: Twenty Seventeen brings your site to life with header video and immersive featured images. With a focus on business sites, it features multiple sections on the front page as well as widgets, navigation and social menus, a logo, and more. Personalize its asymmetrical grid with a custom color scheme and showcase your multimedia content with post formats. Our default theme for 2017 works great in many languages, for any abilities, and on any device.
* Version: 1.4
* License: GNU General Public License v2 or later
* License URI: http://www.gnu.org/licenses/gpl-2.0.html
* Text Domain: twentyseventeen
* Tags: one-column, two-columns, right-sidebar, flexible-header, accessibility-ready, custom-colors, custom-header, custom-menu, custom-logo, editor-style, featured-images, footer-widgets, post-formats, rtl-language-support, sticky-post, theme-options, threaded-comments, translation-ready

* This theme, like WordPress, is licensed under the GPL.
* Use it to make something cool, have fun, and share what you’ve learned with others.
*/

// or at the very least:

/**
* Theme Name: Twenty Seventeen
* Version: 1.4
*/

[/code]

This has a bit of overhead as well due to the function call, but I like the fact that it uses the theme version number so you can easily do Semantic Versioning.

Filename Modification
However, most performance testing tools will recommend you remove query strings altogether to improve caching especially by proxy servers. Here are two automatic methods of doing that:

File Modification Time
[code style=”php”]
<?php

/**
* Enqueue stylesheet
*
* Uses the modification timestamp of the style.css file
*/
function my_theme_enqueue_assets() {
wp_enqueue_style( ‘stylesheet’, str_replace( ‘.css’, ‘.’ . filemtime( get_stylesheet_directory() . ‘/style.css’ ) . ‘.css’, get_stylesheet_uri() ) );
// stylesheet URL: https://mydomain.com/wp-content/themes/my-theme/style.1518212243.css
}
add_action( ‘wp_enqueue_scripts’, ‘my_theme_enqueue_assets’ );
[/code]

WordPress Theme Version
[code style=”php”]

<?php

/**
* Autocalculate and define theme version constant for use in multiple stylesheets/scripts
*/
define( ‘MY_THEME_VERSION’, wp_get_theme()->get( ‘Version’ ) );

/**
* Enqueue stylesheet
*
* Uses the Version header from style.css
*/
function my_theme_enqueue_assets() {
wp_enqueue_style( ‘stylesheet’, str_replace( ‘.css’, ‘.’ . MY_THEME_VERSION . ‘.css’, get_stylesheet_uri() ) );
// stylesheet URL: https://mydomain.com/wp-content/themes/my-theme/style.1.4.css
}
add_action( ‘wp_enqueue_scripts’, ‘my_theme_enqueue_assets’ );
[/code]

Conclusion

No matter which method you choose, setting up caching can greatly improve your site speed. It’s worth a little bit of hassle to ensure your stylesheets and scripts are cached, knowing that with any of these methods, you can easily invalidate those assets.


Function File

WordPress Cache Busting – or what to do if my design changes do not appear?


[code style=”php”]
<?php
/**
* WordPress Cache Busting made simple.
*
* @author Recolize GmbH <service@recolize.com>
* @license http://opensource.org/licenses/GPL-3.0 GNU General Public License Version 3 (GPLv3)
*
* This script is based on
* @see https://medium.com/@futuremediagr/easy-versioning-for-css-and-js-files-in-wordpress-e7dad756586c
* @see https://gist.github.com/ocean90/1966227
*/

function set_custom_ver_css_js($src)
{
// Don’t touch admin scripts.
if (is_admin()) {
return $src;
}

$_src = $src;
if (strpos($_src, ‘//’) === 0) {
$_src = ‘http:’ . $_src;
}

$_src = parse_url($_src);

// Give up if malformed URL.
if (false === $_src) {
return $src;
}

// Check if it’s a local URL.
$wordPressUrl = parse_url(home_url());
if (isset($_src[‘host’]) && $_src[‘host’] !== $wordPressUrl[‘host’]) {
return $src;
}

$filePath = ABSPATH . $_src[‘path’];
if (file_exists($filePath) && strpos($src, ‘ver=’) !== false) {
$src = add_query_arg(‘ver’, filemtime($filePath), $src);
}

return $src;
}

function css_js_versioning()
{
add_filter(‘style_loader_src’, ‘set_custom_ver_css_js’, 9999);
add_filter(‘script_loader_src’, ‘set_custom_ver_css_js’, 9999);
}

add_action(‘init’, ‘css_js_versioning’);

[/code]


Major performance gains are to be had from browser caching CSS. You ensure your server is set up to send headers that tell the browser to hang onto the CSS file for a given amount of time. It’s a best-practice that many if not most sites are doing already.

Hand-in-hand with browser caching is cache busting. Say the browser has the CSS file cached for one year (not uncommon). Then you want to change the CSS. You need a strategy for breaking the cache and forcing the browser to download a new copy of the CSS.

Here are some ways.

The CSS has to be cached for this to matter…

Just to make sure, here’s what some healthy looking headers look like for a cached CSS file:

We’re looking for that Cache-Control and Expires header. I’m not a server config expert. I’d probably look at the H5BP server configs. But here’s some kinda classic Apache/HTAccess ways to get that going:

[code style=”php”]&lt;FilesMatch ".(ico|pdf|flv|jpg|jpeg|png|gif|js|css|swf)(.gz)?$"&gt;
Header set Expires "Thu, 15 Apr 2020 20:00:00 GMT"
&lt;/FilesMatch&gt;[/code]
[code style=”php”]&lt;IfModule mod_expires.c&gt;
ExpiresActive on
ExpiresByType text/css "access plus 1 year"
ExpiresByType application/javascript "access plus 1 year"
&lt;/IfModule&gt;[/code]

Query Strings

Most browsers these days will see a URL with a different query string as a different file and download a fresh copy. Most CDN’s even support and recommend this.

[code style=”php”]<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>link</span> <span class="token attr-name">rel</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>stylesheet<span class="token punctuation">"</span></span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>style.css?v=3.4.1<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>[/code]

Make small change? Change it to:

[code style=”php”]<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>link</span> <span class="token attr-name">rel</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>stylesheet<span class="token punctuation">"</span></span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>style.css?v=3.4.2<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>[/code]

You could potentially make it easier on yourself by setting a server side variable to use in multiple places. Thus changing it would break cache on lots of files at once.

<?php $cssVersion = "3.4.2"; ?>

<link rel="stylesheet" href="global.css?v=<?php echo $cssVersion; ?>">[/code]

Perhaps you could even use Semantic Versioning. You could also define a constant.

Changing File Name

Query strings didn’t always work. Some browsers didn’t see a differnt query string as a different file. And some software (I’ve heard: Squid) wouldn’t cache files with query string. Steve Souders told us not to.

A similar concept was to change the file name itself. Like this in the HTML:

[code style="php"]<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>link</span> <span class="token attr-name">rel</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>stylesheet<span class="token punctuation">"</span></span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>style.232124.css<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>[/code]

You would handle this programmatically, not literally change the file name in your project. Since that file doesn’t actually exist on the server, you’ll need to perform some trickery to route it to the right file. Jeremy Keith covered his technique for this fairly recently.

[code style="php"]RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.+).(d+).(js|css)$ $1.$3 [L][/code]

That tells the server to ignore those numbers in JavaScript and CSS file names, but the browser will still interpret it as a new file whenever I update that number.

He uses Twig, so the templates he uses are ultimately like:

[code class="php"] {% set cssupdate = '20150310' %}

<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>link</span> <span class="token attr-name">rel</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>stylesheet<span class="token punctuation">"</span></span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>/css/main.{{ cssupdate }}.css<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>[/code]]

I’m sure you can imagine a version of that in any backend language (like ASP). Level up by making a build tool or deployment script update the variable itself.

Basing Cache Busting “Number” on File Updated Date

While searching around about this cache busting stuff, you’ll see a lot of advice recommending you use the server to check when the file was last updated to create the cache busting “number” (number, meaning, whatever thing you change to bust cache).

[code style="php"]]<span class="token keyword">function</span> <span class="token function">autoversion</span><span class="token punctuation">(</span><span class="token parameter">$url</span><span class="token punctuation">)</span> <span class="token punctuation">{</span>
$path <span class="token operator">=</span> <span class="token function">pathinfo</span><span class="token punctuation">(</span>$url<span class="token punctuation">)</span><span class="token punctuation">;</span>
$ver <span class="token operator">=</span> <span class="token string">'.'</span><span class="token punctuation">.</span><span class="token function">filemtime</span><span class="token punctuation">(</span>$_SERVER<span class="token punctuation">[</span><span class="token string">'DOCUMENT_ROOT'</span><span class="token punctuation">]</span><span class="token punctuation">.</span>$url<span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token string">'.'</span><span class="token punctuation">;</span>
<span class="token keyword">return</span> $path<span class="token punctuation">[</span><span class="token string">'dirname'</span><span class="token punctuation">]</span><span class="token punctuation">.</span><span class="token string">'/'</span><span class="token punctuation">.</span><span class="token function">str_replace</span><span class="token punctuation">(</span><span class="token string">'.'</span><span class="token punctuation">,</span> $ver<span class="token punctuation">,</span> $path<span class="token punctuation">[</span><span class="token string">'basename'</span><span class="token punctuation">]</span><span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token punctuation">}</span>[/code]
[code style="php"]<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>link</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>&lt;?php autoversion(<span class="token punctuation">'</span>/path/to/theme.css<span class="token punctuation">'</span>); ?&gt;<span class="token punctuation">"</span></span> <span class="token attr-name">rel</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>stylesheet<span class="token punctuation">"</span></span><span class="token punctuation">&gt;</span></span>[/code]]

I can’t speak well to this. It seems to me that asking your server to dig up this information on every pageview would be pretty intensive and dangerous in production. In the past I’ve done things like “I’ll just have PHP output the dimensions of the image in data attributes!” only to find it grinds the server to halt. Anyway, beware.

ETags

ETags kinda seem like a good idea, because the whole point of them is information to check if the browser already has a copy of that file.

But most advice out there says: “turn off your ETags headers”. Yahoo says:

The problem with ETags is that they typically are constructed using attributes that make them unique to a specific server hosting a site. ETags won’t match when a browser gets the original component from one server and later tries to validate that component on a different server, a situation that is all too common on Web sites that use a cluster of servers to handle requests.

Another issue is that they just aren’t as effective as actual caching. In order to check an ETag, a network request still needs to be made. It’s not just the downloading of files that affect performance, it’s all the network negotiation and latency stuff too.

Again, not an expert here, but here’s what’s generally recommended to turn them off in Apache land:

<IfModule mod_headers.c>
Header unset ETag
</IfModule>
FileETag None[/code]

Framework Does It For Us

Rails Asset Pipeline

I have a little experience with the Rails Asset Pipeline and Sprockets. It’s kind of a dream system if you ask me. I link up stylesheets in templates:

<%= stylesheet_link_tag "about/about" %>[/code]

And it produces HTML like:

[code style="php"]<span class="token tag"><span class="token tag"><span class="token punctuation">&lt;</span>link</span> <span class="token attr-name">href</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>http://assets.codepen.io/assets/about/about-7ca9d3db0013f3ea9ba05b9dcda5ede0.css<span class="token punctuation">"</span></span> <span class="token attr-name">media</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>screen<span class="token punctuation">"</span></span> <span class="token attr-name">rel</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>stylesheet<span class="token punctuation">"</span></span> <span class="token attr-name">type</span><span class="token attr-value"><span class="token punctuation">=</span><span class="token punctuation">"</span>text/css<span class="token punctuation">"</span></span> <span class="token punctuation">/&gt;</span></span>[/code]]

That cache busting number only changes when the file changes, so you only break cache on the files that need broken. Plus it has methods for images and JavaScript as well.

WordPress

If you use a page caching tool in WordPress, like W3 Total Cache or something, you probably have to be less afraid of that filemtime business being too server intensive.

Gilbert Pellegrom posted a WordPress-specific technique using it:

[code style="php"]]<span class="token function">wp_register_style</span><span class="token punctuation">(</span> <span class="token string">'screen'</span><span class="token punctuation">,</span> <span class="token function">get_template_directory_uri</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token string">'/style.css'</span><span class="token punctuation">,</span> <span class="token function">array</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">,</span> <span class="token function">filemtime</span><span class="token punctuation">(</span> <span class="token function">get_template_directory</span><span class="token punctuation">(</span><span class="token punctuation">)</span><span class="token punctuation">.</span><span class="token string">'/style.css'</span> <span class="token punctuation">)</span> <span class="token punctuation">)</span><span class="token punctuation">;</span>
<span class="token function">wp_enqueue_style</span><span class="token punctuation">(</span> <span class="token string">'screen'</span> <span class="token punctuation">)</span><span class="token punctuation">;</span>

<span class="token comment">// Example Output: /style.css?ver=1384432580</span>[/code]]

The WordPress plugin Busted! does this same thing behind the scenes. Just does it kinda automatically to everything.

CodeKit

CodeKit doesn’t have a built in method for changing file names, but it does have a way to execute Shell scripts under circumstances you set up.

Michael Russell has a blog post about how you can inject timestamps into file themselves, which I’m sure you could modify to change the filenames instead.

Build Tools

All the popular task runner / build tool thingies have plugins to help change file names. Sufian Rhazi has a post on doing it in raw Node.js as well.

Grunt

Gulp

Broccoli

Within Preprocessors

When linking up assets within other assets (e.g. an image that you link to from within a LESS file, for example) you could put the preprocessor to work. Ben Nadel has a post on doing just that.

Async CSS

With Critical CSS becoming more of a thing, deferred loading of CSS is becoming more of a thing. There are some other reasons to defer loading of CSS as well (perhaps print CSS, or priming cache).

If you’re loading CSS with loadCSS (or perhaps injecting a link tag), you’ll need to update the file name it requests in the JavaScript itself. Different than changing the file name, but not that different.

Anything I missed? What’s your cache busting strategy?


Have you ever made a change to a CSS or JavaScript file on a client’s website, but your client reports they can’t see the changes?

You’ve told them to “hard refresh,” and it didn’t help? You’ve clicked the “Delete Cache” button in the WordPress admin, with no effect?

You’ve flipped your table in rage, and vented to your colleagues about how technically-stupid your client was for not seeing the same things as you did?

I have some good news and some bad news.

The bad news is, it’s kinda your fault this is happening, and your anger is misguided. The good news is, a problem like this is totally within your control to resolve.

I’m talking about cache-busting, and it’s a lot less intimidating than it sounds.

Once you grasp and apply the concept to your own projects, the days of telling your clients to “hard refresh” will be over.

Server-side vs. client-side caching

There are two “high-level” types of caching: server-side and client-side.

With server-side caching in place, a website visitor can refresh a page all day long, but may not see anything different until the website administrator clicks the “Delete Cache” button on the backend, even if HTML markup has been changed in the meantime.

Client-side caching happens on the website visitor’s device. In an attempt to avoid wasting bandwidth, browsers may decline to re-download assets with the same file name.

When we talk of cache-busting, we’re talking about client-side caching. Here’s a step-by-step breakdown of what exactly happens:

  1. Website visitor visits page, browser downloads CSS and JS files.
  2. Website administrator updates CSS and JS files, but nothing else.
  3. Website visitor visits same page, browser sees CSS and JS files of the same exact name, and incorrectly assumes they have not changed.
  4. Website visitor cannot see the effects of whatever CSS and JS the website administrator updated in point #2.

What’s one surefire way we could ensure a file with the “same exact name” isn’t downloaded? You could append a query string to the URL.

Append query strings to your static asset URLs

Here are some examples of query strings appended to the base stylesheet URL of: https://example.com/wp-content/themes/themetry/style.css

  • https://example.com/wp-content/themes/themetry/style.css?lolrandomletters
  • https://example.com/wp-content/themes/themetry/style.css?ver=1.0.1

All you have to do is put a question mark at the end of the URL, and literally anything you want after that. Just as long as the full URL (including query string) hasn’t been used before, the browser will download it again.

In WordPress

Hopefully, you’re using WordPress enqueue functions to load your scripts and styles. Both the wp_enqueue_script and wp_enqueue_style functions have parameters for version number.

For example, the following style enqueue function:

[code style="php"]wp_enqueue_style( 'markov-style', get_stylesheet_uri(), array(), '<strong>20170506</strong>', 'all' );[/code]

Will produce this:

[code style="php"]&lt;link rel='stylesheet' id='markov-style-css' href='https://themetry.com/wp-content/themes/markov/style.css?ver=<strong>20170506</strong>' type='text/css' media='all' /&gt;[/code]

And the following script enqueue function:

[code style="php"]wp_enqueue_script( 'markov-navigation', get_template_directory_uri() . '/js/navigation.js', array(), '20151215', true );[/code]

Will produce this:

[code style="php"]&lt;script type='text/javascript' src='https://themetry.com/wp-content/themes/markov/js/navigation.js?ver=20151215'&gt;&lt;/script&gt;[/code]

To add a touch of automation, you could replace a static value like ‘20151215’ with filemtime() (do not wrap it in quotes, it’s a PHP function) which will use a timestamp based on when the file was last modified.

This is probably best used on a development or staging server, especially if you don’t have any other page caching on your production site.

Other methods of cache busting

There are other methods of cache-busting, but in WordPress, the above outlined method is probably your best bet. CSS Tricks talks about some other ways that you might want to check out.

Scroll to Top