Auto-versioning JavaScript & CSS files
As JavaScript becomes a more integral part to the core functionality of websites, ensuring that a user has a fresh copy of the latest code is as important as ever. Normally a browser will cache certain filetypes (CSS, JS, JPG, etc…) for an indefinite period of time. If the code within one of these cached files is updated, sometimes a browser will not know to reload that file. So, if those changes are important for the functionality of your website, your users may run into issues and not be able to use your site properly. Worse, they likely won’t have any idea what the issue is and will eventually give up and go elsewhere. But have no fear, there’s a solution to this problem… versioning your static files.
By “versioning” these files, I’m referring to changing their filenames from “my_styles.css” to “my_styles.version274.css” for example. The problem with that example is it relies on you renaming the file and updating your includes every time you make a change. That sounds like an nightmare, right? Luckily there’s a neat tactic you can you to make it appear to the browser to be a new file, but it will actually be the same file and will require no effort on your part. This variation of the versioning tactic is often referred to as “Auto-versioning”. Here’s an example…
First, we use the following rewrite rule in .htaccess:
RewriteEngine on
RewriteCond %{REQUEST_FILENAME} !-s # Make the file doesn't actually exist
RewriteRule ^(.*)\.[\d]+\.(css|js)$ $1.$2 [L] # Strip out the version number
Now, we write the following PHP fuction:
/**
* Given a file, i.e. /css/base.css, replaces it with a string containing the
* file's mtime, i.e. /css/base.1221534296.css.
*
* @param $file The file to be loaded. Must be an absolute path (i.e.
* starting with slash).
*/
function auto_version($file)
{
if(strpos($file, '/') !== 0 || !file_exists($_SERVER['DOCUMENT_ROOT'] . $file))
return $file;
$mtime = filemtime($_SERVER['DOCUMENT_ROOT'] . $file);
return preg_replace('{\\.([^./]+)$}', ".$mtime.\$1", $file);
}
Now, wherever you include your CSS, change it from this:
To this:
And when rendered, will appear as:
How does this work? Because that mod_rewrite rule above will strip out the timestamp, your server will deliver /css/base.css as the requested file. But to the user’s browser, it will appear as a different file whenever it is updated.
Neat!
Note: Some strategies to fix this caching issue is to append a version # onto the end of the css or JavaScript file with a querystring, such as:
This method will actually prevent the file from being cached, period! This is bad. You want the benefits of caching. You don’t want to make your users pull down 100k of JavaScript & CSS files on every page load. More importantly, your servers could start to strain under the additional load.
Why is it not cached? See Section 13 from the HTTP Specification, in particular Section 13.9
13.9 Side Effects of GET and HEAD
Unless the origin server explicitly prohibits the caching of their responses, the application of GET and HEAD methods to any resources SHOULD NOT have side effects that would lead to erroneous behavior if these responses are taken from a cache. They MAY still have side effects, but a cache is not required to consider such side effects in its caching decisions. Caches are always expected to observe an origin server’s explicit restrictions on caching.
We note one exception to this rule: since some applications have traditionally used GETs and HEADs with query URLs (those containing a “?” in the rel_path part) to perform operations with significant side effects, caches MUST NOT treat responses to such URIs as fresh unless the server provides an explicit expiration time. This specifically means that responses from HTTP/1.0 servers for such URIs SHOULD NOT be taken from a cache. See section 9.1.1 for related information.