[javascript] Cache busting via params

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?

This question is related to javascript html css caching

The answer is


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.


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.


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.


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


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.


<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> 

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.


 <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  ###

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.


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

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.


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).


Examples related to javascript

need to add a class to an element How to make a variable accessible outside a function? Hide Signs that Meteor.js was Used How to create a showdown.js markdown extension Please help me convert this script to a simple image slider Highlight Anchor Links when user manually scrolls? Summing radio input values How to execute an action before close metro app WinJS javascript, for loop defines a dynamic variable name Getting all files in directory with ajax

Examples related to html

Embed ruby within URL : Middleman Blog Please help me convert this script to a simple image slider Generating a list of pages (not posts) without the index file Why there is this "clear" class before footer? Is it possible to change the content HTML5 alert messages? Getting all files in directory with ajax DevTools failed to load SourceMap: Could not load content for chrome-extension How to set width of mat-table column in angular? How to open a link in new tab using angular? ERROR Error: Uncaught (in promise), Cannot match any routes. URL Segment

Examples related to css

need to add a class to an element Using Lato fonts in my css (@font-face) Please help me convert this script to a simple image slider Why there is this "clear" class before footer? How to set width of mat-table column in angular? Center content vertically on Vuetify bootstrap 4 file input doesn't show the file name Bootstrap 4: responsive sidebar menu to top navbar Stylesheet not loaded because of MIME-type Force flex item to span full row width

Examples related to caching

Disable nginx cache for JavaScript files How to prevent Browser cache on Angular 2 site? Curl command without using cache Notepad++ cached files location Laravel 5 Clear Views Cache Write-back vs Write-Through caching? Tomcat 8 throwing - org.apache.catalina.webresources.Cache.getResource Unable to add the resource Chrome - ERR_CACHE_MISS How do I use disk caching in Picasso? How to clear gradle cache?