Skip to content Skip to footer navigation

Dynamic Cachebreaking Solution for Perch CMS

This article covers a solution for dynamically cache breaking static assets like JS and CSS, without needing a build process like Grunt or Gulp.

About Caching

Caching files is typically a good thing since it encourages the browser to reuse assets that have already been seen and cached to a temporary memory file.

A general best practice is to tell the browser to cache assets for a long time by setting header expires. Doing this means viewers do not need to re-download assets on repeat visits.

Problems with Caching

Caching can be a problem when you update your assets but the browser continues to serve the old ones because of the "header expires" you've set.

Caching Solutions

The solution to this caching problem is known as "cache-breaking", or sometimes "cache-busting". Cachebreaking involves changing the filename so the browser thinks there is a new asset to download.

For example, instead of trying to load style.min.css, we try to load style.min.1487201825.css. The browser thinks this is a brand new file, so it effectively resets the cache.

The typical way to deal with Cachebreaking is through an automated build process that changes the file names for you. You might do this through Grunt or Gulp for example.

If you're using a CMS such as Perch, however, since Perch runs on PHP which is dynamic, we can create our own caching solution using some PHP functions and rewrite rules.

Dynamic Cachebreaking in Perch

Part 1 — Serving a Filename Based on its Modified Time

The first thing we want to do is understand when our CSS or JS file was last modified. By understanding this we can prevent needlessly resetting/breaking the cache. We can get the last modified time by using the PHP filemtime function ("file modified time").

So in the PHP file which you're using to serve your assets (e.g. head.php) you would add a variable that gets the "last modified" time of a particular asset.

Get the last modified time of your CSS

The example below would get the last modified time of our minified CSS file. You should change this path to whatever's relevant for your particular setup.

$CSSversion = filemtime($SERVER['DOCUMENTROOT'].'/assets/css/style.min.css');

This variable would output a unique "last modified" time string such as 1487201825

We then want to call that variable when we call our asset. Here is an example:

<link rel="stylesheet" href="/assets/css/style.min.<?php echo $CSSversion ?>.css" type="text/css">

This would translate to:

<link rel="stylesheet" href="/assets/css/style.min.1487201825.css" type="text/css">

Get the last modified time of your JS

The example below would get the last modified time of our minified JS file. You should change this path to whatever's relevant for your particular setup.

$JSversion = filemtime($SERVER['DOCUMENTROOT'].'/assets/js/script.min.js');

This variable would output a unique "last modified" time string such as 1487201825.

We then want to call that variable when we call our asset. Here is an example:

<script src="/assets/js/script.min.<?php echo $JSversion ?>.js"></script>

This would translate to:

<script src="/assets/js/script.min.1487201825.js"></script>

In the above examples I'm covering both our CSS and JS assets but you can keep repeating the steps for different assets that you'd like to monitor.

So this takes care of breaking the cache whenever certain files have changed.

Part 2 — Rewriting Requests to Point to the Original Minified File

While the cache would effectively be reset/broken at this point, the problem is we don't actually have a file called style.min.1487201825.css! Instead, our file is just called style.min.css.

This is where you would ideally have a build process that would rename the file for you.

With an htaccess trick, we don't need that though. If you're running your site on an Apache server, the htaccess file is what's used to control any redirect or rewrite rules.

Your htaccess file is normally a "hidden" file at the root of your site, so make sure you have hidden files shown. Alternatively, if you have added a directory to a code editor like Sublime Text it will show hidden files for you.

In your htaccess file you'll need something like this:

<IfModule mod_rewrite.c>

    RewriteEngine on

    Options +FollowSymlinks

    # Cache Breaking

    # ---------------

    RewriteRule (assets/)([^.]*).min.+.(css|js)$ $1$2.min.$3

</IfModule>

As before, you should change the path to whatever's relevant for your particular setup. It's important we specify some of the paths so that we "ringfence" the rule. This prevents accidentally rewriting a plugin's CSS file or another directory in Perch.

An important note is that if you're using Perch Runway you'll need the cache breaking rules to appear above some of Runway's rules, so you might have something like this:

<IfModule mod_rewrite.c>
    RewriteEngine on
    Options +FollowSymlinks

    # Cache Breaking
    # ---------------
    RewriteRule (assets/)([^.]*).min.+.(css|js)$ $1$2.min.$3

    # Perch Runway <-- needs to come after Cache Breaking
    # --------------
    RewriteCond %{REQUEST_URI} !^/login
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule .* /login/core/runway/start.php [L]
</IfModule>

So what is our rule doing? It's looking for any path that starts with assets/ and ends with min.css or min.js. Htaccess is then rewriting that path, removing any middle ".something"'s, which means the "file modified time" number we inserted is written out.

If you inspect your page, we'll still see our style.min.1487201825.css example is showing but behind the scenes, it's been rewritten to style.min.css

You can play with this rule on Regexr (note that I've had to wrap square brackets around forward slashes on Regexr, but you shouldn't need these in your htaccess).

Notes

  1. You could skip step 2 if you want to cache break using a query string e.g. <link rel="stylesheet" href="/assets/css/style.min.css?v=1487201825" type="text/css">, however, this does not seem as favourable as renaming the file because some delivery services do not automatically treat files with a query string as a new asset

  2. This is a great article for further reading on automatic versioning

Credits

Thanks to Martin Underhill for his suggestion to use PHP to cache-bust, Duncan Revell for his Regex help, and Caden Peters for his suggestion to rewrite in htaccess rather than use a query string.