Cache busting in Modx

One of the biggest pains in web development has to be caching! Can't live with it but most definitely can't live with out. I was recently involved in a discussion around ways to cache bust in Modx and came up with a really simple little snippet that adds the file modified timestamp to a query parameter for the file. Check it out

PHP
<?php

$file = $modx->getOption('file', $scriptProperties);
$output = $file; 
$filePath = str_replace('//','/',MODX_BASE_PATH . $file);

if(!is_null($filePath) && file_exists($filePath)) {
    $output .= '?v=' . filemtime($filePath);
}
return $output;


// Usage: 
// <link rel="stylesheet" href="[[!cacheBust? &file=`/assets/css/style.css`]]">
// <script src="[[!cacheBust? &file=`/assets/js/script.js`]]"></script>

Call the snippet uncached (so it runs every time the page loads), then when you make a change to the file it'll update the cache bust parameter:

Output:

HTML
<link rel="stylesheet" href="/assets/css/style.css?v=1688118541">
<script src="/assets/js/script.js?v=1688118541"></script>

There is quite a lot of discussion around best practices with cache busting assets, should you use a query parameter i.e. style.css?v=12345 or rename the file style.12345.css. If you swing more to file renaming you could update the snippet to:

PHP
<?php

$file = $modx->getOption('file', $scriptProperties);
$output = $file; 
$filePath = str_replace('//','/',MODX_BASE_PATH . $file);

$fileArr = explode('.', $file);
$ext = array_pop($fileArr);

if(!is_null($filePath) && file_exists($filePath)) {
    // Add the file modified timestamp to the filename
    array_push($fileArr, filemtime($filePath));
}
// Add the extension back
array_push($fileArr, $ext);
// turn the array back to a string
return implode('.', $fileArr);

// This will return something like
// <link rel="stylesheet" href="/assets/css/style.1688118541.css">

Then you can use htaccess to rewrite the file name like so

BASh
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.+).(\d+).(js|css)$ $1.$3 [L]

However, a word of caution! In Strategies for Cache-Busting CSS by Chris Coyier he advises against this method due to the additional load filemtime() puts on the server.

The alternative method is to create a system/ context setting (or use an Extra like Client Config) specifically for cache busting then you can use it in your templates like: <link rel="stylesheet" href="/assets/css/style.css?v=[[!++css_bust:tag]]">