155

We want to cache bust on production deploys, but not waste a bunch of time off the bat figuring out a system for doing so. My thought was to apply a param to the end of css and js files with the current version number:

<link rel="stylesheet" href="base_url.com/file.css?v=1.123"/>

Two questions: Will this effectively break the cache? Will the param cause the browser to then never cache the response from that url since the param indicates that this is dynamic content?

Brad Herman
  • 9,665
  • 7
  • 28
  • 30

12 Answers12

147

The param ?v=1.123 indicates a query string, and the browser will therefore think it is a new path from, say, ?v=1.0. Thus causing it to load from file, not from cache. As you want.

And, the browser will assume that the source will stay the same next time you call ?v=1.123 and should cache it with that string. So it will remain cached, however your server is set up, until you move to ?v=1.124 or so on.

Marshall
  • 4,716
  • 1
  • 19
  • 14
  • 6
    Quoting Steve Souders: "To gain the benefit of caching by popular proxies, avoid revving with a querystring and instead rev the filename itself." The full explanation can be found here: http://www.stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring/ – lao Feb 18 '16 at 13:48
  • 34
    That blog post is approaching a decade old now. Do you think that cache providers and CDNs have yet to accommodate it? Squid seems to be able to cache documents with query strings [now](http://serverfault.com/questions/401201/squid-cannot-cache-static-files-with-query-string/401205). – jeteon Mar 09 '16 at 22:44
  • 1
    Maybe this helps somebody: Personally, I use the file modification timestamp as an 'automatic' version parameter, eg. `` – oelna Feb 28 '17 at 18:50
  • I don't personally understand why but Lara Hogan (Swanson) (engineering manager at Etsy) does not recommended using query parameters to cache-busting. I think it has to do with cache proxies between the user and the server. – Sam Rueby Jul 20 '17 at 13:01
  • @lao the explanation relies on the fact that squid doesn't not cache resources with a querystring. But this is no longer the case since at least 2015 according to https://serverfault.com/questions/401201/squid-cannot-cache-static-files-with-query-string#comment807522_401205 – ElBidoule Aug 31 '22 at 07:49
42

Two questions: Will this effectively break the cache?

Yes. Even Stack Overflow use this method, although I remember that they (with their millions of visitors per day and zillions of different client and proxy versions and configurations) have had some freak edge cases where even this was not enough to break the cache. But the general assumption is that this will work, and is a suitable method to break caching on clients.

Will the param cause the browser to then never cache the response from that url since the param indicates that this is dynamic content?

No. The parameter will not change the caching policy; the caching headers sent by the server still apply, and if it doesn't send any, the browser's defaults.

Community
  • 1
  • 1
Pekka
  • 442,112
  • 142
  • 972
  • 1,088
  • 1
    @spender I can't find the reference right now I'm afraid, there was a lengthy blog article or SO answer where Jeff Atwood talks about it (IIRC) – Pekka May 23 '14 at 06:50
  • 2
    @spender I have read that some proxy servers (either old, or can be configured to) ignore the query string when caching. – MrWhite Aug 22 '14 at 12:03
  • 2
    @spender - i've heard the same, and I think changing the filename, or the path is the best option. It might be easiest to just let move all your static files under a versioned folder name, for example, `/static/v22/file.css`, as you could do multiple files with a single folder rename, e.g. `/static/v23/file.css` and `/static/v23/mystuff.js` – Brad Parks Oct 06 '15 at 16:18
  • Wow, I just checked this page source and even in 2022 StackOverflow still uses query strings to bust its asset cache! – duhaime Oct 10 '22 at 22:55
25

It is safer to put the version number in the actual filename. This allows multiple versions to exist at once so you can roll out a new version and if any cached HTML pages still exist that are requesting the older version, they will get the version that works with their HTML.

Note, in one of the largest versioned deployments anywhere on the internet, jQuery uses version numbers in the actual filename and it safely allows multiple versions to co-exist without any special server-side logic (each version is just a different file).

This busts the cache once when you deploy new pages and new linked files (which is what you want) and from then on those versions can be effectively cached (which you also want).

jfriend00
  • 683,504
  • 96
  • 985
  • 979
  • I agree with that, but it's much easier to just have Sinatra append ?v=<%=VERSION%> to all css and js requests versus having to control every file individually. Eventually we will switch to sinatra-assetpack, which will pre-process and compress all files and actually append a version # to the filename, which will then let us control them individually much easier. – Brad Herman Mar 13 '12 at 21:56
  • 2
    I agree putting the version number in the file name is the ultimate safest solution if you want to make 10000% sure, but I don't follow the "multiple versions to exist at once" argument. A URL with a query parameter is distinct from the same URL with a different query parameter. They should be treated as two different resources by the client; if they aren't, the client is broken. – Pekka Mar 13 '12 at 21:58
  • 2
    @Pekka - the version number thing can allow multiple versions to exist at once, but that requires server cooperation to map the query parameter to the correct actual file. I do not think that is what the OP is doing here and there's little reason to require that complication when modifying the filename is much simpler and needs no server cooperation. Obviously both can work. – jfriend00 Mar 13 '12 at 22:15
13

As others have said, cache busting with a query param is usually considered a Bad Idea (tm), and has been for a long time. It's better to reflect the version in the file name. Html5 Boilerplate recommends against using the query string, among others.

That said, of the recommendations I have seen which cited a source, all seem to take their wisdom from a 2008 article by Steve Souders. His conclusions are based on the behaviour of proxies at the time, and they may or may not be relevant these days. Still, in the absence of more current information, changing the file name is the safe option.

hashchange
  • 7,029
  • 1
  • 45
  • 41
9

It will bust the cache once, after the client has downloaded the resource every other response will be served from client cache unless:

  1. the v parameter is updated.
  2. the client clears their cache
ncremins
  • 9,140
  • 2
  • 25
  • 24
6

In general this should be fine, but it's possible for this to not work if there is an intermediate cache (a proxy) that is configured to ignore the request parameters.

For example, if you are serving static content through Akamai CDN, it can be configured to ignore request parameters to prevent cache busting using this method.

Ken Liu
  • 22,503
  • 19
  • 75
  • 98
5

It very much depends on quite how robust you want your caching to be. For example, the squid proxy server (and possibly others) defaults to not caching URLs served with a querystring - at least, it did when that article was written. If you don't mind certain use cases causing unnecessary cache misses, then go ahead with query params. But it's very easy to set up a filename-based cache-busting scheme which avoids this problem.

Bobby Jack
  • 15,689
  • 15
  • 65
  • 97
  • 7
    The squid proxy that was cited in the Steve Souders article has changed their default caching policy. Since version 2.7 (May 2008) and version 3.1 (March 2010), default behavior is to cache dynamic content. – Josh Rack Jun 12 '15 at 13:45
5

Found a comparison of the 2 techniques (query string vs file name) here:

Version as a querystring has two problems.

First, it may not always be a browser that implements caching through which we need to bust. It is said that certain (possibly older) proxies do ignore the querystring with respect to their caching behavior.

Second, in certain more complex deployment scenarios, where you have multiple frontend and/or multiple backend servers, an upgrade is anything but instantaneous. You need to be able to serve both the old and the new version of your assets at the same time. See for example how this affects you when using Google App Engine.

Community
  • 1
  • 1
user
  • 17,781
  • 20
  • 98
  • 124
3

Another similar approach is to use htaccess mod_rewrite to ignore part of the path when serving the files. Your never-cached index page references the latest path to the files.

From a development perspective it's as easy as using params for the version number, but it's as robust as the filename approach.

Use the ignored part of the path for the version number, and the server just ignores it and serves the uncached file.

1.2.3/css/styles.css serves the same file as css/styles.css since the first directory is stripped and ignored by the htaccess file

Including versioned files

<?php
  $version = "1.2.3";
?>

<html>
  <head>
    <meta http-equiv="cache-control" content="max-age=0" />
    <meta http-equiv="cache-control" content="no-cache" />
    <meta http-equiv="expires" content="0" />
    <meta http-equiv="expires" content="Tue, 01 Jan 1980 1:00:00 GMT" />
    <meta http-equiv="pragma" content="no-cache" />
    <link rel="stylesheet" type="text/css" href="<?php echo $version ?>/css/styles.css">
  </head>
  <body>
    <script src="<?php echo $version ?>/js/main.js"></script>
  </body>
</html>

Note that this approach means you need to disable caching of your index page - Using <meta> tags to turn off caching in all browsers?

.htaccess file

RewriteEngine On

# if you're requesting a file that exists, do nothing
RewriteCond %{REQUEST_FILENAME} !-f 
# likewise if a directory that exists, do nothing
RewriteCond %{REQUEST_FILENAME} !-d 

# otherwise, rewrite foo/bar/baz to bar/baz - ignore the first directory
RewriteRule ^[^/]+/(.+)$ $1 [L] 

You could take the same approach on any server platform that allows url rewriting

(rewrite condition adapted from mod_rewrite - rewrite directory to query string except /#!/)

... and if you need cache busting for your index page / site entry point, you could always use JavaSript to refresh it.

Community
  • 1
  • 1
alexanderbird
  • 3,847
  • 1
  • 26
  • 35
2
<script type="text/javascript">
// front end cache bust

var cacheBust = ['js/StrUtil.js', 'js/protos.common.js', 'js/conf.js', 'bootstrap_ECP/js/init.js'];   
for (i=0; i < cacheBust.length; i++){
     var el = document.createElement('script');
     el.src = cacheBust[i]+"?v=" + Math.random();
     document.getElementsByTagName('head')[0].appendChild(el);
}
</script> 
  • During development / testing of new releases, the cache can be a problem because the browser, the server and even sometimes the 3G telco (if you do mobile deployment) will cache the static content (e.g. JS, CSS, HTML, img). You can overcome this by appending version number, random number or timestamp to the URL e.g: JSP: In case you're running pure HTML (instead of server pages JSP, ASP, PHP) the server won't help you. In browser, links are loaded before the JS runs, therefore you have to remove the links and load them with JS – Conete Cristian Jun 09 '16 at 01:55
  • I don't think this will load the JS files in order, synchronously. – Stealth Rabbi Apr 11 '18 at 19:35
2

Hope this should help you to inject external JS file

<script type="text/javascript"> 
var cachebuster = Math.round(new Date().getTime() / 1000); 
document.write('<scr'+'ipt type="text/javascript" src="external.js?cb=' +cachebuster+'"></scr' + 'ipt>');
</script>

Source - Cachebuster code in JavaScript

Vinit Kadkol
  • 1,221
  • 13
  • 12
  • I don't know who downvoted this, it's a perfectly acceptable solution. Maybe it's worth to add that is a very hard way to bypass the cache, for every request, not only when a resource is updated and you upgrade the version number. It may be useful for certain requests for which you want to be sure to never acquire cached data in response. – DrLightman Jan 07 '21 at 11:42
-1
 <script>
    var storedSrcElements = [
         "js/exampleFile.js",
         "js/sampleFile.js",
         "css/style.css"
          ];

    var head= document.getElementsByTagName('head')[0];
    var script;
    var link;
    var versionNumberNew = 4.6;

    for(i=0;i<storedSrcElements.length;i++){
     script= document.createElement('script');
     script.type= 'text/javascript';
     script.src= storedSrcElements[i] + "?" + versionNumberNew;
     head.appendChild(script);
    }     


     </script> 


       ### Change the version number  (versionNumberNew) when you want the new files to be loaded  ###
Teja
  • 25
  • 9