How to force the browser to reload cached CSS/JS files?

How to force the browser to reload cached CSS/JS files?

I have noticed that some browsers (in particular, Firefox and Opera) are very zealous in using cached copies of .css and .js files, even between browser sessions. This leads to a problem when you update one of these files but the user’s browser keeps on using the cached copy.
The question is: what is the most elegant way of forcing the user’s browser to reload the file when it has changed?
Ideally, the solution would not force the browser to reload the file on every visit to the page. I will post my own solution as an answer, but I am curious if anyone has a better solution and I’ll let your votes decide.
Update :
After allowing discussion here for a while, I have found John Millikin and da5id’s suggestion to be useful. It turns out there is a term for this: auto-versioning.
I have posted a new answer below which is a combination of my original solution and John’s suggestion.
Another idea that was suggested by SCdF would be to append a bogus query string to the file. (Some Python code to automatically use the timestamp as a bogus query string was submitted by pi.). However, there is some discussion as to whether or not the browser would cache a file with a query string. (Remember, we want the browser to cache the file and use it on future visits. We only want it to fetch the file again when it has changed.)
Since it is not clear what happens with a bogus query string, I am not accepting that answer.

Solutions/Answers:

Solution 1:

Update: Rewritten to incorporate suggestions from John Millikin and da5id. This solution is written in PHP, but should be easily adapted to other languages.

Update 2: Incorporating comments from Nick Johnson that the original .htaccess regex can cause problems with files like json-1.3.js. Solution is to only rewrite if there are exactly 10 digits at the end. (Because 10 digits covers all timestamps from 9/9/2001 to 11/20/2286.)

First, we use the following rewrite rule in .htaccess:

RewriteEngine on
RewriteRule ^(.*)\.[\d]{10}\.(css|js)$ $1.$2 [L]

Now, we write the following PHP function:

/**
 *  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:

<link rel="stylesheet" href="/css/base.css" type="text/css" />

To this:

<link rel="stylesheet" href="<?php echo auto_version('/css/base.css'); ?>" type="text/css" />

This way, you never have to modify the link tag again, and the user will always see the latest CSS. The browser will be able to cache the CSS file, but when you make any changes to your CSS the browser will see this as a new URL, so it won’t use the cached copy.

This can also work with images, favicons, and JavaScript. Basically anything that is not dynamically generated.

Solution 2:

Simple Client-side Technique

In general, caching is good.. So there are a couple of techniques, depending on whether you’re fixing the problem for yourself as you develop a website, or whether you’re trying to control cache in a production environment.

General visitors to your website won’t have the same experience that you’re having when you’re developing the site. Since the average visitor comes to the site less frequently (maybe only a few times each month, unless you’re a Google or hi5 Networks), then they are less likely to have your files in cache, and that may be enough. If you want to force a new version into the browser, you can always add a query string to the request, and bump up the version number when you make major changes:

<script src="/myJavascript.js?version=4"></script>

This will ensure that everyone gets the new file. It works because the browser looks at the URL of the file to determine whether it has a copy in cache. If your server isn’t set up to do anything with the query string, it will be ignored, but the name will look like a new file to the browser.

On the other hand, if you’re developing a website, you don’t want to change the version number every time you save a change to your development version. That would be tedious.

So while you’re developing your site, a good trick would be to automatically generate a query string parameter:

<!-- Development version: -->
<script>document.write('<script src="/myJavascript.js?dev=' + Math.floor(Math.random() * 100) + '"\><\/script>');</script>

Adding a query string to the request is a good way to version a resource, but for a simple website this may be unnecessary. And remember, caching is a good thing.

It’s also worth noting that the browser isn’t necessarily stingy about keeping files in cache. Browsers have policies for this sort of thing, and they are usually playing by the rules laid down in the HTTP specification. When a browser makes a request to a server, part of the response is an EXPIRES header.. a date which tells the browser how long it should be kept in cache. The next time the browser comes across a request for the same file, it sees that it has a copy in cache and looks to the EXPIRES date to decide whether it should be used.

So believe it or not, it’s actually your server that is making that browser cache so persistent. You could adjust your server settings and change the EXPIRES headers, but the little technique I’ve written above is probably a much simpler way for you to go about it. Since caching is good, you usually want to set that date far into the future (a “Far-future Expires Header”), and use the technique described above to force a change.

If you’re interested in more info on HTTP or how these requests are made, a good book is “High Performance Web Sites” by Steve Souders. It’s a very good introduction to the subject.

Solution 3:

Google’s mod_pagespeed plugin for apache will do auto-versioning for you. It’s really slick.

It parses HTML on its way out of the webserver (works with PHP, rails, python, static HTML — anything) and rewrites links to CSS, JS, image files so they include an id code. It serves up the files at the modified URLs with a very long cache control on them. When the files change, it automatically changes the URLs so the browser has to re-fetch them. It basically just works, without any changes to your code. It’ll even minify your code on the way out too.

Solution 4:

Instead of changing the version manually, I would recommend you use an MD5 hash of the actual CSS file.

So your URL would be something like

http://mysite.com/css/[md5_hash_here]/style.css

You could still use the rewrite rule to strip out the hash, but the advantage is that now you can set your cache policy to “cache forever”, since if the URL is the same, that means that the file is unchanged.

You can then write a simple shell script that would compute the hash of the file and update your tag (you’d probably want to move it to a separate file for inclusion).

Simply run that script every time CSS changes and you’re good. The browser will ONLY reload your files when they are altered. If you make an edit and then undo it, there’s no pain in figuring out which version you need to return to in order for your visitors not to re-download.

Solution 5:

Not sure why you guys are taking so much pain to implement this solution.

All you need to do if get the file’s modified timestamp and append it as a querystring to the file

In PHP i would do it as:

<link href="mycss.css?v=<?= filemtime('mycss.css') ?>" rel="stylesheet">

filemtime is a PHP function that returns the file modified timestamp.

Solution 6:

You can just put ?foo=1234 at the end of your css / js import, changing 1234 to be whatever you like. Have a look at the SO html source for an example.

The idea there being that the ? parameters are discarded / ignored on the request anyway and you can change that number when you roll out a new version.


Note: There is some argument with regard to exactly how this affects caching. I believe the general gist of it is that GET requests, with or without parameters should be cachable, so the above solution should work.

However, it is down to both the web server to decide if it wants to adhere to that part of the spec and the browser the user uses, as it can just go right ahead and ask for a fresh version anyway.