1147

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.

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 have found John Millikin's 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.)

Jesse Nickles
  • 1,435
  • 1
  • 17
  • 25
Kip
  • 107,154
  • 87
  • 232
  • 265
  • I have this in my .htaccess, and never any problems with cached files: `ExpiresActive On ExpiresDefault "modification"`. – Frank Conijn - Support Ukraine May 15 '14 at 14:06
  • 2
    I'd definitely agree that adding versioning info to the file's URL is by far the best way to go. It works, all the time, for everyone. But, if you're not using it, and you just need to reload that one CSS or JS file occasionally in your own browser... just open it in its own tab and hit SHIFT-reload (or CTRL-F5)! You can do effectively the same thing using JS by loading a file in a (hidden) iframe, waiting till it loads, and then calling `iframe.contentWindow.location.reload(true)`. See method (4) of https://stackoverflow.com/a/22429796/999120 - that's about images, but the same applies. – Doin Dec 27 '15 at 05:17
  • 6
    I really appreciate the way this question was asked and has been updated since then. It completely described what I should expect in the answers. I am going to follow this approach in my questions from now on. Cheers! – rd22 Aug 01 '16 at 09:46
  • 2
    For reference: [da5id's's deleted answer](https://stackoverflow.com/questions/118884/how-to-force-the-browser-to-reload-cached-css-and-javascript-files/118901#118901) is *"If an update is big/important enough I generally change the name of the file."*. – Peter Mortensen Nov 28 '20 at 03:48
  • If the changes are not very often, I have a suggestion. Just change the file name and edit the source code to include the new file name. Then there’s no cached file for the browser to read. – SK-the-Learner Feb 24 '21 at 10:00
  • I wrote an article about that, a method I created and used that it's really working about CDN with GitHub, auto versioning, auto-update, etc https://dany1980.medium.com/cdn-e-aggiornamenti-automatici-degli-assets-che-restano-in-cache-d362a99f054d just translate from Italian with google – Daniele Rugginenti Sep 16 '22 at 07:23

57 Answers57

493

This solution is written in PHP, but it should be easily adapted to other languages.

The original .htaccess regex can cause problems with files like json-1.3.js. The 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. works on all type of paths.
 */
function auto_version($file) {
  if($file[0] !== '/') {
    $file = rtrim(str_replace(DIRECTORY_SEPARATOR, '/', dirname($_SERVER['PHP_SELF'])), '/') . '/' . $file;
  }
  
  if (!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.

Kia Faraji
  • 17
  • 4
Kip
  • 107,154
  • 87
  • 232
  • 265
  • 18
    My own static content server does exactly the same, except I use a parameter for versioning (base.css?v=1221534296) rather than a filename change (base.1221534296.css). I suspect your way may be a little bit more efficient though. Very cool. – Jens Roland Jun 02 '11 at 20:55
  • Great answer. I wonder if this can be used when static files are in other servers. Isn't it good practice to store static files in other servers? – Sanghyun Lee Jul 29 '11 at 12:47
  • 4
    @Kip: Very slick solution. URL rewriting obviously has much more to offer than just prettyfying urls. – James P. Aug 06 '11 at 12:51
  • @Sangdol: What you could do with PHP is a copy and md5 to check if a file has changed. If the file's md5 has changed => version++. – James P. Aug 06 '11 at 12:52
  • 1
    A note: if you are using Zend Framework, this rewrite rule should go on top of other rules. – understack Jan 24 '12 at 10:51
  • This is nice, but I'm working on a system where some of the css/js are pulled in from another server, so via a url and not an absolute path. This is just a thought, but I'm thinking that the php function can be extended to cover this case by calling php's get_headers(), getting the last modified date, converting that to an mtime looking string and doing the preg_replace as-is. – eflat Apr 20 '12 at 00:45
  • FWIW, I used the above code: `auto_version()` and the `rewriteRules` on many small-ish JS/CSS files (~20, mostly 1kb-100kb). This apparently puts a large load on my browser cache (Chrome v19) b/c the fan on my Mac (OS X Lion) starts going berserk. I was able to correct it by setting the cache as disabled in the dev tools. Anyone know why this is happensing? – tim peterson Jun 22 '12 at 12:10
  • 40
    I see a problem with this, that it accesses the filesystem many times - exactly - number of links * number of requests/sec... that may or may not be a problem for you. – Tomáš Fejfar Sep 24 '12 at 23:34
  • @JensRoland: I'm curious about your comment. It seems to me that by using query strings one wouldn't need the extra overhead of the `mod_rewrite` rule. Is there any specific browser that doesn't re-fetch the file with a different query string or is there any other reason for your suspicion? – Alix Axel Nov 27 '12 at 01:52
  • 3
    @AlixAxel: No, browsers will re-fetch it when the parameter changes, but some public proxies won't cache files with url parameters, so the best practice is to include the version in the path. And the mod_rewrite overhead is miniscule compared to every other performance bottleneck in WPO – Jens Roland Nov 27 '12 at 11:54
  • @JensRoland: Fair enough. I still prefer the query string approach though, it's simpler and it has less dependencies for one to worry about. – Alix Axel Nov 27 '12 at 12:17
  • No one want to reinvent the wheel, is there simple API/library for it? I mean without a full MVC/Rail like framework and can simply plug and replace into existing code. – Dennis C Jun 21 '13 at 08:26
  • Just to note we were using *.css?v=2342323423 and it did not work until 3 refreshes and a cache cleanse. – Michael J. Calkins Oct 07 '13 at 23:39
  • I modified this to play with "/"'s better. https://gist.github.com/clouddueling/6877043 – Michael J. Calkins Oct 07 '13 at 23:50
  • 1
    @TomášFejfar this wouldn't be a problem if you are caching your php output in some sort of reverse proxy like Squid or Varnish. – Patrick James McDougle Oct 24 '13 at 02:48
  • How does this look with NGINX? If someone can answer that then they can answer this question too: http://stackoverflow.com/questions/9423138/rewriting-urls-for-static-js-and-css-files-using-nginx-server – tim peterson Dec 16 '13 at 23:01
  • 1
    @MichaelCalkins I had that same problem. Don't use a variable name with a value. Just use `*.css?2342323423`. That seemed to work for me. – Gavin Jun 04 '14 at 22:39
  • 8
    Is the first `file_exists` check really necessary? `filemtime` will return false on failure, so why not just assign the filemtime value to a variable and check if it's false before renaming the file? That would cut down on one unnecessary file operation which would really add up. – Gavin Jun 04 '14 at 23:21
  • 2
    I trimmed the function down to this: `$filetime = strpos($file, '/') === 0 ? filemtime($_SERVER['DOCUMENT_ROOT'] . $file) : false; if ($filetime) return preg_replace('{\\.([^./]+)$}', ".$filetime).\$1", $file); else return $file;` – Gavin Jun 04 '14 at 23:36
  • I've done something like this (jsp-tag based), but it doesn't solve the problem of one of these javascript resources programmatically including .css, images, other javascript or other resources. For that, it almost requires the javascript to be rewritten at build time or.. the magical google method mentioned in another answer. – ticktock Aug 26 '14 at 23:01
  • Clever solution ! But using it with a public CDN is not easy. The filename rewriting has to be done on CDN files also. Whereas with Query string solution CDN can handle it (Amazon Cloudfront option : "Forward Query Strings") – kheraud Jan 23 '15 at 15:13
  • Great post talking about the query string caching issue : http://bizcoder.com/caching-resources-with-query-strings – kheraud Jan 26 '15 at 10:40
  • on one hand - this looks good. But on the other hand - if the new developer takes this project, lets say he needs to run it on another server, he puts project files and runs in the browser. And sees - wtf, why these styles and javacripts do not work? He does not know that there has to be some configuration in apache server to work. Or one solution would be maybe - on failed file load - do logging with message - you need to configure apache to use file rewriting. – Dariux Mar 19 '15 at 10:33
  • 2
    @TomášFejfar - when it can be a problem that it it touches file system? File system is touched a lot anyway - for example database. Can really checking file last modified time be long conparing to other stuff like database? And btw - I see in docs that result of filemtime is cached http://php.net/manual/en/function.filemtime.php – Dariux Mar 19 '15 at 14:14
  • One more thing for others who come here in the future: in PHP, the double backslash in the regex works but appears to be technically incorrect. See https://regex101.com/r/eA4zP3/1 vs https://regex101.com/r/eA4zP3/2. – seanvalencourt Aug 05 '15 at 18:19
  • 1
    @arcodesign The .htaccess regex changes `base.1221534296.css` to `base.css`, which is the actual file name on the file system. The regex in PHP doesn't have a double-backslash--we have a PHP string containing a regex. \\ in a PHP string encodes a single backslash. – Kip Aug 05 '15 at 18:22
  • so say you are using varnish as a caching proxy, would any of the code in the answer need to be altered? @PatrickJamesMcDougle – John Jackson Nov 07 '16 at 20:05
  • @JohnJackson not if you have a proper mechanism for cache invalidation – Patrick James McDougle Nov 09 '16 at 23:52
  • What are the performance gains by doing it this way compared to just appending the filemtime as a query to the file? – doz87 Dec 21 '16 at 05:13
  • @Kip How to change the file version no. only when it changes not every time ? – iit2011081 Jul 28 '17 at 08:04
  • Thanks @Kip. But I don't know how to rewrite rule in .htaccess in Javascript – Tuan Nguyen Apr 06 '18 at 06:56
  • This is the only legitimate solution. Adding query strings to references to static content is a bad idea, which is why Google, GTMetrix, etc. all raise flags for doing so. Changing the name is perfectly elegant, and forces a browser to update ONLY when you need it. Query strings break caching for some browsers, which should NEVER be seen as a solution (that's a hack, not a solution). – Nate I Apr 24 '18 at 18:02
  • @NateI even with the potential performance loss due to multiple requests? I think query strings can be a perfect temporary solution just to make sure that users don't see a broken website during a major transition. – jarrodwhitley Apr 15 '19 at 20:43
  • @JarrodW. I can definitely agree that sometimes you just need a temporary solution, I just don't like anything that only works in certain browsers. The browser needs to make a request for the JS anyway; we're not adding a request at all here. Adding a query string still requires the browser to request it from the server, and the whole point of bypassing caching is to force that request in the first place. – Nate I Apr 16 '19 at 14:40
  • 1
    @Kip there is no benefit in writing `\d` inside of a character class. Also, it is probably better practice to use a pattern delimiter that doesn't have a special meaning in regex -- this will improve human readability. The double slash escaping can be single in php. Perhaps avoid a capture group. For your consideration: https://3v4l.org/PPbUW – mickmackusa Dec 05 '19 at 07:04
  • Why not ot use ETag or Last-Modified headers? – Alex78191 Mar 02 '22 at 11:27
229

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

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
keparo
  • 33,450
  • 13
  • 60
  • 66
  • 3
    The quick trick of generating query string with Javascript works great during active development. I did the same thing with PHP. – Alan Turing May 21 '13 at 18:04
  • 2
    This is the easiest way of accomplishing the original poster's desired result. The mod_rewrite method works well if you want to force a reload of the .css or .js file EVERY time you load the page. This method still allows caching until you actually change the file and really want it to force reload. – scott80109 Feb 19 '14 at 00:22
  • @keparo, I have ample number of the jquery in all the pages if i am going to change this manually it will take a month. If you can help me to resolve in all without coding to each page. – cracker Nov 26 '14 at 11:42
  • I've tried this solution with different browsers : adding a version number at the end of the JS file URL. Interestingly, Opera 25.0, Firefox 34.0 and Chrome 39.0.2171.65 will NOT keep the file in cache as soon as there is the version number at the end, even if the number does not change. IE 11.0 and Safari 5.1.7 work as expected though. – Chris Neve Jan 19 '15 at 14:07
  • would it work to write this query string script in a separate js file rather than add to all pages in the site and if so, how? – Mia Sno Mar 06 '15 at 01:48
  • If you feel unlucky, you should consider using Date.now() instead of a random value. – The_Black_Smurf Jun 01 '15 at 13:31
  • 2
    This doesn't seem to work for my CSS when I use: `` – Noumenon Jul 24 '15 at 17:26
  • The pointers here were really handy. I have an SPA and all I need to do is update a single index.html on publishing. It would still be horrible to do manually. I wrote some code and an app that do it - I wrote an answer to the question (which is probably right at the end because its new - not because it is bad) which describes how it works and includes the code. I hope it helps someone else developing in similar situations – statler May 12 '16 at 08:26
  • This is the best and most reliable solution listed for the simple reason it avoids server manipulation, unreliable HTTP Header caching, and preserves the native caching browsers do automatically. If you start hijacking the server and rewriting url's you create a potential mess of calls to and from your users thats not managed. This simple query string trick has been around since the 1990's so most kids dont know its there. The browser and server cache images and scripts natively use http headers. That's why AngularJS routing and other circus tricks are not needed if you understand caching. – Stokely Jul 30 '17 at 02:52
  • 6
    This is not a viable solution. A good number of browsers will simply refuse to cache anything with a query string on it. This is the reason why Google, GTMetrix, and similar tools will raise a flag if you have query strings on references to static content. While it is certainly a decent solution for development, it is absolutely not a solution for production. Also, the browser controls the caching, not the server. The server simply SUGGESTS when it should be refreshed; a browser does not HAVE to listen to the server (and often doesn't). Mobile devices are a prime example of this. – Nate I Apr 24 '18 at 17:59
  • 1
    The document.write solution works too good, now I can't set a breakpoint in Chrome because the url keeps changing and thus keeps refreshing and losing my breakpoints! – CoderSteve Jul 26 '19 at 15:30
  • I prefer this solution as it does not require a server side fix. I no longer use server-side code (ie, .php, .asp, etc). I develop strictly client side now with only ajax calls going to the server to fetch data, and js code and css styles. And Chrome seems to treat css like a bank treats gold bars. MINE! – KWallace Sep 28 '19 at 20:51
  • Simple and effective, I love it ! – bN_ Dec 24 '21 at 14:01
115

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, Ruby on Rails, Python, static HTML -- anything) and rewrites links to CSS, JavaScript, 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.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Leopd
  • 41,333
  • 31
  • 129
  • 167
  • 1
    That's great, but still in beta. Can it be used for enterprise service? – Sanghyun Lee Jul 15 '11 at 04:00
  • 28
    This is WRONG (auto-fiddling with the source) when it is clearly a browser-issue. Give us (developers) a real brain-wipe-refresh: +F5 – T4NK3R Sep 20 '11 at 12:50
  • 26
    mod_pagespeed is functionally equivalent to a completely automatic build/compile step for your html/css/js. I think you'd be hard pressed to find any serious developers who think build systems are intrinsically wrong, or that there's anything wrong with it being completely automatic. The analogy of a clean build is to clear mod_pagespeed's cache: http://code.google.com/p/modpagespeed/wiki/FAQ#How_do_I_clear_the_cache_on_my_server? – Leopd Sep 20 '11 at 17:38
  • 4
    @T4NK3R mod_pagespeed doesn't have to do anything with your source to do cache management, it was simply mentioned that it *can* help with things like minification. As to whether or not it's "WRONG", that completely subjective. It may be wrong for you, but that doesn't mean it's instirinsically _bad_. – Madbreaks Aug 03 '12 at 18:10
  • However, this only move the problem from the client to the server, since if aggregated stylesheets are updated but pagespeed cache is not user will get stale stylesheets. – chirale May 25 '13 at 08:52
  • VERY slick! With the latest release, all css and js links are aggregated into one liners with a version number appended. I followed these instructions for my debian box, https://www.digitalocean.com/community/articles/how-to-get-started-with-mod_pagespeed-with-apache-on-an-ubuntu-and-debian-cloud-server – FredTheWebGuy Apr 26 '14 at 17:22
  • 2
    It works with nginx too though you have to build it from source : https://developers.google.com/speed/pagespeed/module/build_ngx_pagespeed_from_source – Rohit Jan 22 '15 at 07:41
  • 1
    @T4NK3R it's not only us developers that use websites. So ctrl+F5 is not a great solution for the average user and we don't want them to see an old version usually. – Eoin Feb 13 '18 at 14:06
  • Note: mod_pagespeed is now archived. – robsch May 02 '23 at 14:04
99

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.

levik
  • 114,835
  • 27
  • 73
  • 90
  • 1
    unfortunately I do not know how to implement it. Advice please ...more details... – Michael Phelps Oct 20 '14 at 09:56
  • An implementation in shell, ruby, etc would be great – Peter Dec 09 '14 at 19:28
  • 3
    Very nice solution.. but I think it is resources consuming to calculate the hash of the file in every file request (css, js, images,html..etc) for every single page visit. – DeepBlue May 02 '15 at 22:18
  • This is a standard solution for those using js or css bundling with gulp, grunt or webpack, the implementation differs for each solution, but hashing your files as a build step is common and suggested for modern bundled apps – Brandon Culley Apr 13 '18 at 22:45
  • @DeepBlue - answer says *"run that script every time CSS changes"*. That's NOT on every page visit. OTOH The answer leaves out major details - how the changed hash becomes part of the URL? I don't know... – ToolmakerSteve Apr 14 '19 at 21:35
  • Use the file timestamp instead of MD5 hash. All you need is a unique value: `File.GetLastWriteTime(filePath).Ticks.ToString()` – Katie Kilian Jun 29 '20 at 15:31
99

I am not sure why you guys/gals 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.

robsch
  • 9,358
  • 9
  • 63
  • 104
Phantom007
  • 2,079
  • 4
  • 25
  • 37
  • You can just use `mycss.css?1234567890`. – Gavin Jun 04 '14 at 23:43
  • 5
    very elegant, though I have slightly modified it to ``, just in case some of the arguments on this thread about caching URL's with GET variables (in the format suggested) are correct – luke_mclachlan Dec 24 '15 at 13:52
  • further to my last comment, I've seen that wordpress uses `?ver=` so who knows! – luke_mclachlan Dec 24 '15 at 14:25
  • 1
    Great solution. In addition for me I found that filemtime didn't work for a fully qualified domain name (FQDN) so I used the FQDN for the href part and $_SERVER["DOCUMENT_ROOT"] for the filemtime part. EX: "/> – rrtx2000 Aug 18 '16 at 16:56
  • Great thanks. Simple and good. Here it is in Python: progpath = os.path.dirname(sys.argv[0]) def versionize(file): timestamp = os.path.getmtime('%s/../web/%s' % (progpath, file)) return '%s?v=%s' % (file, timestamp) print ' \ % versionize('css/main.css') – dlink Dec 24 '17 at 22:00
  • 1
    There are multiple things wrong with this approach. First, this eliminates caching on this file entirely. The question required forcing a refresh of the asset WHEN IT CHANGED, not preventing caching entirely (which is a VERY bad idea in general). Second, query strings on static files are a bad idea as some browsers will not cache them at all, others cache them no matter what the query string is. Overall, this is a very junior solution and questioning why people deliberated over a proper solution (as opposed to a hack) just shows a general lack of understanding on the matter. – Nate I Apr 16 '19 at 15:28
  • Absolutely perfect quick solution. – Matt Jan 31 '20 at 23:52
  • thanks, this worked for me and was super simple, I used @luke_mclachlan slightly edited version. – Katrina C Jun 22 '21 at 15:34
  • Unfortunately, it doesn't work for ES6 modules that are imported – Samuel Gfeller Nov 18 '22 at 11:27
58

You can just put ?foo=1234 at the end of your CSS / JavaScript import, changing 1234 to be whatever you like. Have a look at the Stack Overflow 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.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
SCdF
  • 57,260
  • 24
  • 77
  • 113
  • Nonsense. The query-string (aka. GET parameters) are part of the URL. They can, and will be cached. This is a good solution. – troelskn Sep 23 '08 at 15:39
  • 9
    @troelskn: The HTTP 1.1 spec says otherwise (with respect to GET and HEAD requests with query params): caches MUST NOT treat responses to such URIs as fresh unless the server provides an explicit expiration time. See http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.9 – Michael Johnson Sep 23 '08 at 18:52
  • 4
    I tried the query string type of versioning with all major browsers and they DO cache the file, specs or not. However, I think it's better to use the style.TIMESTAMP.css format without abusing query strings anyway because there's still the possibility that caching proxy software WILL NOT cache the file. – Tomas Andrle Oct 08 '09 at 15:24
  • 36
    Worth noting, for whatever reason, that Stackoverflow itself uses the query string method. – jason May 01 '10 at 20:13
  • Android 2.2 browsers will fail on this condition and not load the CSS at all. – Petrogad Jan 30 '13 at 21:13
  • @Petrogad - well that's surely a bug in the android browser then. Has a bugreport been filed? – UpTheCreek Apr 15 '13 at 10:31
  • 2
    Have verified that using ?=parameter will not make browsers re-fetch cached file when parameter changes. The only way is to change the file name itself programatically at the server end as answered by Kip – arunskrish Jun 02 '13 at 06:10
  • @boscharun: try ?v=parameter ins – Marcel Burkhard Nov 25 '14 at 13:17
  • @arunskrish, did you actually change the parameter value for each run, such as a time-stamp and/or a big random integer? Otherwise, the mere existence of a parameter will not force an update by itself. And, which browser brand & version did it fail on? Thanks. – FloverOwe Jul 05 '23 at 18:58
43

I've heard this called "auto versioning". The most common method is to include the static file's modification time somewhere in the URL, and strip it out using rewrite handlers or URL configurations:

See also:

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
John Millikin
  • 197,344
  • 39
  • 212
  • 226
  • 3
    Thanks, I guess this was another case where my idea has been discussed, I just didn't know what it was called so I never found it on Google searches. – Kip Sep 23 '08 at 12:12
34

The 30 or so existing answers are great advice for a circa 2008 website. However, when it comes to a modern, single-page application (SPA), it might be time to rethink some fundamental assumptions… specifically the idea that it is desirable for the web server to serve only the single, most recent version of a file.

Imagine you're a user that has version M of a SPA loaded into your browser:

  1. Your CD pipeline deploys the new version N of the application onto the server
  2. You navigate within the SPA, which sends an XMLHttpRequest (XHR) to the server to get /some.template
  • (Your browser hasn't refreshed the page, so you're still running version M)
  1. The server responds with the contents of /some.template — do you want it to return version M or N of the template?

If the format of /some.template changed between versions M and N (or the file was renamed or whatever) you probably don't want version N of the template sent to the browser that's running the old version M of the parser.†

Web applications run into this issue when two conditions are met:

  • Resources are requested asynchronously some time after the initial page load
  • The application logic assumes things (that may change in future versions) about resource content

Once your application needs to serve up multiple versions in parallel, solving caching and "reloading" becomes trivial:

  1. Install all site files into versioned directories: /v<release_tag_1>/…files…, /v<release_tag_2>/…files…
  2. Set HTTP headers to let browsers cache files forever
  • (Or better yet, put everything in a CDN)
  1. Update all <script> and <link> tags, etc. to point to that file in one of the versioned directories

That last step sounds tricky, as it could require calling a URL builder for every URL in your server-side or client-side code. Or you could just make clever use of the <base> tag and change the current version in one place.

† One way around this is to be aggressive about forcing the browser to reload everything when a new version is released. But for the sake of letting any in-progress operations to complete, it may still be easiest to support at least two versions in parallel: v-current and v-previous.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Michael Kropat
  • 14,557
  • 12
  • 70
  • 91
  • Michael - your comment is very relevant. I cam here precisely trying to find a solution for my SPA. I got some pointers, but had to come up with a solution myself. In the end, I was really happy with what I came up with so I wrote a blog post and an answer to this question (including code). Thanks for the pointers – statler May 12 '16 at 08:23
  • Great comment. I can't understand while people keep talking about cache busting and HTTP caching as the real solution to web sites caching problems without comenting the new problems of SPAs, as if this were a marginal case. – David Casillas Jun 30 '17 at 15:46
  • 2
    Excellent response and absolutely ideal strategy! And bonus points for mentioning the `base` tag! As for supporting old code: this isn't always a possibility, nor is it always a good idea. New versions of code may support breaking changes to other pieces of an app or may involve emergency fixes, vulnerability patches, and so on. I have yet to implement this strategy myself, but I've always felt overall architecture should allow for deploys to tag an old version as `obsolete` and force a reload the next time an asynchronous call is made (or just forcefully de-auth all sessions via WebSockets). – Jonny Asmar Dec 23 '17 at 00:30
  • Nice to see a well thought answer in regards to single page applications. – Nate I Apr 16 '19 at 15:33
  • That's "blue-green deployment" if you want to search for more info. – Fil Jul 16 '19 at 11:10
18

In Laravel (PHP) we can do it in the following clear and elegant way (using file modification timestamp):

<script src="{{ asset('/js/your.js?v='.filemtime('js/your.js')) }}"></script>

And similar for CSS

<link rel="stylesheet" href="{{asset('css/your.css?v='.filemtime('css/your.css'))}}">

Example HTML output (filemtime return time as as a Unix timestamp)

<link rel="stylesheet" href="assets/css/your.css?v=1577772366">
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Kamil Kiełczewski
  • 85,173
  • 29
  • 368
  • 345
  • what is the output of this command in html? And what if I need to renew only versions like ?v=3, ?v=4 and etc. - Doesn't force browser to load css everytime user enters the website – Gediminas Šukys Sep 28 '17 at 05:38
  • *filemtime*: "This function returns the time when the data blocks of a file were being written to, that is, the time when the content of the file was changed." src: http://php.net/manual/en/function.filemtime.php – Kamil Kiełczewski Sep 28 '17 at 11:12
16

Don’t use foo.css?version=1!

Browsers aren't supposed to cache URLs with GET variables. According to http://www.thinkvitamin.com/features/webapps/serving-javascript-fast, though Internet Explorer and Firefox ignore this, Opera and Safari don't! Instead, use foo.v1234.css, and use rewrite rules to strip out the version number.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
airrob
  • 235
  • 2
  • 3
  • 1
    First of all browsers don't cache, thats a function of HTTP. Why would http care about the structure of a URI? Is there an officail reference to a spec that states the HTTP cacheing should understand the semantics of a URI so that it won't cache items with a query string? – AnthonyWJones Sep 23 '08 at 08:17
  • 14
    A web browser that includes the functionality of caching objects (check your browser's cache directory). HTTP is a protocol including directives from servers to clients (proxies, browsers, spiders etc) suggesting cache control. – tzot Sep 29 '08 at 15:47
  • The thinkvitamin.com link is broken (the domain seems to exist, but there isn't any reposnse). – Peter Mortensen Nov 22 '20 at 00:58
  • archive.org copy of the article: https://web.archive.org/web/20060523204906/http://www.thinkvitamin.com/features/webapps/serving-javascript-fast from May 2006 , but according to this answer here https://stackoverflow.com/a/85386/338265 The claim about Opera & Safari *not caching* was false. But we're more interested in whether browsers bust their caches when seeing different query params (most browsers) (in 2021). – Harry Wood Nov 12 '21 at 00:05
  • @AnthonyWJones, that is patently false. Brave caches files even with caching turned off in evwry way imaginable. You have no idea the hoops I go through to bust Brave caching files. And if you use `import` in a JavaScript file, caching _CANNOT_ be turned off at all. The only solution is to change the filename, and then all debugging breakpoints are destroyed each time in dev tools. – SO_fix_the_vote_sorting_bug Apr 08 '23 at 00:13
14

Here is a pure JavaScript solution

(function(){

    // Match this timestamp with the release of your code
    var lastVersioning = Date.UTC(2014, 11, 20, 2, 15, 10);
 
    var lastCacheDateTime = localStorage.getItem('lastCacheDatetime');

    if(lastCacheDateTime){
        if(lastVersioning > lastCacheDateTime){
            var reload = true;
        }
    }

    localStorage.setItem('lastCacheDatetime', Date.now());

    if(reload){
        location.reload(true);
    }

})();

The above will look for the last time the user visited your site. If the last visit was before you released new code, it uses location.reload(true) to force page refresh from server.

I usually have this as the very first script within the <head> so it's evaluated before any other content loads. If a reload needs to occurs, it's hardly noticeable to the user.

I am using local storage to store the last visit timestamp on the browser, but you can add cookies to the mix if you're looking to support older versions of IE.

Lloyd Banks
  • 35,740
  • 58
  • 156
  • 248
  • 1
    I tried something like this, this will only work on the reloaded page, but if the site has multi pages sharing same css/images then other pages will still use old resources. – DeepBlue May 03 '15 at 00:29
  • Dumb question: can I simply inject this code into my very basic static HTML page? – David.P Nov 10 '22 at 14:23
12

The RewriteRule needs a small update for JavaScript or CSS files that contain a dot notation versioning at the end. E.g., json-1.3.js.

I added a dot negation class [^.] to the regex, so .number. is ignored.

RewriteRule ^(.*)\.[^.][\d]+\.(css|js)$ $1.$2 [L]
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Nick Johnson
  • 914
  • 10
  • 11
  • 2
    Thanks for the input! Since I wrote this post I've been burned by this too. My solution was to only rewrite if the last part of the filename contains exactly ten digits. (10 digits covers all timestamps from 9/9/2001 to 11/20/2286.) I've updated my answer to include this regex: `^(.*)\.[\d]{10}\.(css|js)$ $1.$2` – Kip Aug 05 '10 at 21:07
  • I understand regex, but I don't understand what problem you are solving with `[^.]` here. Also, there is no benefit to writing `\d` inside of a character class -- `\d+` will do the same thing. As posted, your pattern will be matching any number of characters (greedily), then a literal dot, then a non-dot, then one-or-more digits, then a dot, then `css` or `js`, then the end of the filename. No match for your sample input: https://regex101.com/r/RPGC62/1 – mickmackusa Dec 05 '19 at 06:52
11

Interesting post. Having read all the answers here combined with the fact that I have never had any problems with "bogus" query strings (which I am unsure why everyone is so reluctant to use this) I guess the solution (which removes the need for Apache rewrite rules as in the accepted answer) is to compute a short hash of the CSS file contents (instead of the file datetime) as a bogus querystring.

This would result in the following:

<link rel="stylesheet" href="/css/base.css?[hash-here]" type="text/css" />

Of course, the datetime solutions also get the job done in the case of editing a CSS file, but I think it is about the CSS file content and not about the file datetime, so why get these mixed up?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Michiel
  • 2,624
  • 1
  • 18
  • 14
11

For ASP.NET 4.5 and greater you can use script bundling.

The request http://localhost/MvcBM_time/bundles/AllMyScripts?v=r0sLDicvP58AIXN_mc3QdyVvVj5euZNzdsa2N1PKvb81 is for the bundle AllMyScripts and contains a query string pair v=r0sLDicvP58AIXN_mc3QdyVvVj5euZNzdsa2N1PKvb81. The query string v has a value token that is a unique identifier used for caching. As long as the bundle doesn't change, the ASP.NET application will request the AllMyScripts bundle using this token. If any file in the bundle changes, the ASP.NET optimization framework will generate a new token, guaranteeing that browser requests for the bundle will get the latest bundle.

There are other benefits to bundling, including increased performance on first-time page loads with minification.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
user3738893
  • 425
  • 1
  • 6
  • 18
9

For my development, I find that Chrome has a great solution.

https://superuser.com/a/512833

With developer tools open, simply long click the refresh button and let go once you hover over "Empty Cache and Hard Reload".

This is my best friend, and is a super lightweight way to get what you want!

Frank Bryce
  • 8,076
  • 4
  • 38
  • 56
8

I have not found the client-side DOM approach creating the script node (or CSS) element dynamically:

<script>
    var node = document.createElement("script");
    node.type = "text/javascript";
    node.src = 'test.js?' + Math.floor(Math.random()*999999999);
    document.getElementsByTagName("head")[0].appendChild(node);
</script>
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
GreQ
  • 989
  • 1
  • 8
  • 5
  • What *have* you found then? Can you make that more clear? Preferably by [editing your answer](https://stackoverflow.com/posts/37538586/edit) (but ***without*** "Edit:", "Update:" or similar), not here in comments. – Peter Mortensen Nov 28 '20 at 06:16
8

Thanks to Kip for his perfect solution!

I extended it to use it as an Zend_view_Helper. Because my client run his page on a virtual host I also extended it for that.

/**
 * Extend filepath with timestamp to force browser to
 * automatically refresh them if they are updated
 *
 * This is based on Kip's version, but now
 * also works on virtual hosts
 * @link http://stackoverflow.com/questions/118884/what-is-an-elegant-way-to-force-browsers-to-reload-cached-css-js-files
 *
 * Usage:
 * - extend your .htaccess file with
 * # Route for My_View_Helper_AutoRefreshRewriter
 * # which extends files with there timestamp so if these
 * # are updated a automatic refresh should occur
 * # RewriteRule ^(.*)\.[^.][\d]+\.(css|js)$ $1.$2 [L]
 * - then use it in your view script like
 * $this->headLink()->appendStylesheet( $this->autoRefreshRewriter($this->cssPath . 'default.css'));
 *
 */
class My_View_Helper_AutoRefreshRewriter extends Zend_View_Helper_Abstract {

    public function autoRefreshRewriter($filePath) {

        if (strpos($filePath, '/') !== 0) {

            // Path has no leading '/'
            return $filePath;
        } elseif (file_exists($_SERVER['DOCUMENT_ROOT'] . $filePath)) {

            // File exists under normal path
            // so build path based on this
            $mtime = filemtime($_SERVER['DOCUMENT_ROOT'] . $filePath);
            return preg_replace('{\\.([^./]+)$}', ".$mtime.\$1", $filePath);
        } else {

            // Fetch directory of index.php file (file from all others are included)
            // and get only the directory
            $indexFilePath = dirname(current(get_included_files()));

            // Check if file exist relativ to index file
            if (file_exists($indexFilePath . $filePath)) {

                // Get timestamp based on this relativ path
                $mtime = filemtime($indexFilePath . $filePath);

                // Write generated timestamp to path
                // but use old path not the relativ one
                return preg_replace('{\\.([^./]+)$}', ".$mtime.\$1", $filePath);
            } else {
                return $filePath;
            }
        }
    }
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
lony
  • 6,733
  • 11
  • 60
  • 92
7

I recently solved this using Python. Here is the code (it should be easy to adopt to other languages):

def import_tag(pattern, name, **kw):
    if name[0] == "/":
        name = name[1:]
    # Additional HTML attributes
    attrs = ' '.join(['%s="%s"' % item for item in kw.items()])
    try:
        # Get the files modification time
        mtime = os.stat(os.path.join('/documentroot', name)).st_mtime
        include = "%s?%d" % (name, mtime)
        # This is the same as sprintf(pattern, attrs, include) in other
        # languages
        return pattern % (attrs, include)
    except:
        # In case of error return the include without the added query
        # parameter.
        return pattern % (attrs, name)

def script(name, **kw):
    return import_tag('<script %s src="/%s"></script>', name, **kw)

def stylesheet(name, **kw):
    return import_tag('<link rel="stylesheet" type="text/css" %s href="/%s">', name, **kw)

This code basically appends the files time-stamp as a query parameter to the URL. The call of the following function

script("/main.css")

will result in

<link rel="stylesheet" type="text/css"  href="/main.css?1221842734">

The advantage of course is that you do never have to change your HTML content again, touching the CSS file will automatically trigger a cache invalidation. It works very well and the overhead is not noticeable.

huyz
  • 2,297
  • 3
  • 25
  • 34
pi.
  • 21,112
  • 8
  • 38
  • 59
  • could os.stat() create a bottleneck? – hoju Jul 23 '12 at 03:19
  • @Richard stat could be a bottleneck if the disk is very slow and the requests are very many. In that case you could cache the timestamp somewhere in memory and purge this cache upon every new deployment. Yet this complexity will not be necessary in the majority of use cases. – pi. Jul 23 '12 at 11:06
  • I know this is ancient, but for anybody reading, a timestamp is far too aggressive. It means you never have any caching at all, and if you want that, you can manage that with custom headers for static files. – LarryBud Jan 11 '21 at 22:43
  • 2
    @LarryBud: It's the timestamp of the file, not the current timestamp. You'll definitely have caching. – pi. Jan 14 '21 at 08:56
7

Say you have a file available at:

/styles/screen.css

You can either append a query parameter with version information onto the URI, e.g.:

/styles/screen.css?v=1234

Or you can prepend version information, e.g.:

/v/1234/styles/screen.css

IMHO, the second method is better for CSS files, because they can refer to images using relative URLs which means that if you specify a background-image like so:

body {
    background-image: url('images/happy.gif');
}

Its URL will effectively be:

/v/1234/styles/images/happy.gif

This means that if you update the version number used, the server will treat this as a new resource and not use a cached version. If you base your version number on the Subversion, CVS, etc. revision this means that changes to images referenced in CSS files will be noticed. That isn't guaranteed with the first scheme, i.e. the URL images/happy.gif relative to /styles/screen.css?v=1235 is /styles/images/happy.gif which doesn't contain any version information.

I have implemented a caching solution using this technique with Java servlets and simply handle requests to /v/* with a servlet that delegates to the underlying resource (i.e. /styles/screen.css). In development mode I set caching headers that tell the client to always check the freshness of the resource with the server (this typically results in a 304 if you delegate to Tomcat's DefaultServlet and the .css, .js, etc. file hasn't changed) while in deployment mode I set headers that say "cache forever".

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Walter Rumsby
  • 7,435
  • 5
  • 41
  • 36
  • Simply adding a folder which you can rename when necessary will work if you only use relative URLs. And then you make sure to redirect to the proper folder from the base folder, i.e. in PHP: ``. – Gruber Sep 20 '12 at 08:56
  • 2
    Using the second method, a change to a CSS will invalidate cached copies of all images referenced with relative URLs, which may or may not be desirable. – TomG Nov 06 '13 at 15:55
7

You could simply add some random number with the CSS and JavaScript URL like

example.css?randomNo = Math.random()
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Ponmudi VN
  • 1,493
  • 1
  • 18
  • 22
7

Google Chrome has the Hard Reload as well as the Empty Cache and Hard Reload option. You can click and hold the reload button (in Inspect Mode) to select one.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
ajithes1
  • 433
  • 2
  • 11
  • 28
  • 1
    To clarify, by "Inspect Mode", they are referring to "Dev Tools" aka F12, aka ctrl+shift+i, aka `ant menu` > `More Tools` > `Developer Tools`, aka `right click` > `Inspect Element`. There is also a setting buried away somewhere in dev tools (I forget the location) to hard reload on every reload. – Jonny Asmar Dec 23 '17 at 00:20
6

You can force a "session-wide caching" if you add the session-id as a spurious parameter of the JavaScript/CSS file:

<link rel="stylesheet" src="myStyles.css?ABCDEF12345sessionID" />
<script language="javascript" src="myCode.js?ABCDEF12345sessionID"></script>

If you want a version-wide caching, you could add some code to print the file date or similar. If you're using Java you can use a custom-tag to generate the link in an elegant way.

<link rel="stylesheet" src="myStyles.css?20080922_1020" />
<script language="javascript" src="myCode.js?20080922_1120"></script>
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
helios
  • 13,574
  • 2
  • 45
  • 55
6

For ASP.NET I propose the following solution with advanced options (debug/release mode, versions):

Include JavaScript or CSS files this way:

<script type="text/javascript" src="Scripts/exampleScript<%=Global.JsPostfix%>" />
<link rel="stylesheet" type="text/css" href="Css/exampleCss<%=Global.CssPostfix%>" />

Global.JsPostfix and Global.CssPostfix are calculated by the following way in Global.asax:

protected void Application_Start(object sender, EventArgs e)
{
    ...
    string jsVersion = ConfigurationManager.AppSettings["JsVersion"];
    bool updateEveryAppStart = Convert.ToBoolean(ConfigurationManager.AppSettings["UpdateJsEveryAppStart"]);
    int buildNumber = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.Revision;
    JsPostfix = "";
#if !DEBUG
    JsPostfix += ".min";
#endif
    JsPostfix += ".js?" + jsVersion + "_" + buildNumber;
    if (updateEveryAppStart)
    {
        Random rand = new Random();
        JsPosfix += "_" + rand.Next();
    }
    ...
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Ivan Kochurkin
  • 4,413
  • 8
  • 45
  • 80
5

If you're using Git and PHP, you can reload the script from the cache each time there is a change in the Git repository, using the following code:

exec('git rev-parse --verify HEAD 2> /dev/null', $gitLog);
echo '  <script src="/path/to/script.js"?v='.$gitLog[0].'></script>'.PHP_EOL;
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
readikus
  • 369
  • 1
  • 6
  • 17
5

Simply add this code where you want to do a hard reload (force the browser to reload cached CSS and JavaScript files):

$(window).load(function() {
    location.reload(true);
});

Do this inside the .load, so it does not refresh like a loop.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Karan Shaw
  • 1,206
  • 10
  • 11
5

For development: use a browser setting: for example, Chrome network tab has a disable cache option.

For production: append a unique query parameter to the request (for example, q?Date.now()) with a server-side rendering framework or pure JavaScript code.

// Pure JavaScript unique query parameter generation
//
//=== myfile.js

function hello() { console.log('hello') };

//=== end of file

<script type="text/javascript">
    document.write('<script type="text/javascript" src="myfile.js?q=' + Date.now() + '">
    // document.write is considered bad practice!
    // We can't use hello() yet
</script>')

<script type="text/javascript">
    hello();
</script>
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
patrick
  • 16,091
  • 29
  • 100
  • 164
  • This example needs editing. The idea is good, but there are confusions with beginning and end script tags in the above. – Magnus Oct 02 '20 at 08:25
4

It seems all answers here suggest some sort of versioning in the naming scheme, which has its downsides.

Browsers should be well aware of what to cache and what not to cache by reading the web server's response, in particular the HTTP headers - for how long is this resource valid? Was this resource updated since I last retrieved it? etc.

If things are configured 'correctly', just updating the files of your application should (at some point) refresh the browser's caches. You can for example configure your web server to tell the browser to never cache files (which is a bad idea).

A more in-depth explanation of how that works is in How Web Caches Work.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
commonpike
  • 10,499
  • 4
  • 65
  • 58
4

Just use server-side code to add the date of the file... that way it will be cached and only reloaded when the file changes.

In ASP.NET:

<link rel="stylesheet" href="~/css/custom.css?d=@(System.Text.RegularExpressions.Regex.Replace(File.GetLastWriteTime(Server.MapPath("~/css/custom.css")).ToString(),"[^0-9]", ""))" />

<script type="text/javascript" src="~/js/custom.js?d=@(System.Text.RegularExpressions.Regex.Replace(File.GetLastWriteTime(Server.MapPath("~/js/custom.js")).ToString(),"[^0-9]", ""))"></script>

This can be simplified to:

<script src="<%= Page.ResolveClientUrlUnique("~/js/custom.js") %>" type="text/javascript"></script>

By adding an extension method to your project to extend Page:

public static class Extension_Methods
{
    public static string ResolveClientUrlUnique(this System.Web.UI.Page oPg, string sRelPath)
    {
        string sFilePath = oPg.Server.MapPath(sRelPath);
        string sLastDate = System.IO.File.GetLastWriteTime(sFilePath).ToString();
        string sDateHashed = System.Text.RegularExpressions.Regex.Replace(sLastDate, "[^0-9]", "");

        return oPg.ResolveClientUrl(sRelPath) + "?d=" + sDateHashed;
    }
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
4

For developers with this problem while developing and testing:

Remove caching briefly.

"keep caching consistent with the file" .. it's way too much hassle ..

Generally speaking, I don't mind loading more - even loading again files which did not change - on most projects - is practically irrelevant. While developing an application - we are mostly loading from disk, on localhost:port - so this increase in network traffic issue is not a deal breaking issue.

Most small projects are just playing around - they never end-up in production. So for them you don't need anything more...

As such if you use Chrome DevTools, you can follow this disable-caching approach like in the image below:

How to force chrome to reload cached files

And if you have Firefox caching issues:

How to force asset reload on Firefox

How to disable caching in Firefox while in development

Do this only in development. You also need a mechanism to force reload for production, since your users will use old cache invalidated modules if you update your application frequently and you don't provide a dedicated cache synchronisation mechanism like the ones described in the answers above.

Yes, this information is already in previous answers, but I still needed to do a Google search to find it.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
AIon
  • 12,521
  • 10
  • 47
  • 73
  • 1
    OP asked something and replied something else. It's not about force load in local but in production and you cannot ask end users to follow above to disable cache etc. – Jitendra Pancholi Apr 01 '20 at 13:39
  • 1
    hi, if it worked that would be great, but for some reason it doesn't works... I'm on firefox, and checking this option doesn't prevent firefox of not seeing recent changes in html (but opening in a new private windows works, which is not a suitable workflow). Do you have any ideas ? – hugogogo Mar 29 '22 at 10:23
  • in case someone comes to this comment because the solution above looked great but didn't works : i noticed that short cuts like Ctrl-R or f5 are not enough to completely reload a page, but giving focus in address bar (Ctrl_L or by clicking in it), then press enter works, or easier : Ctrl-Shift-R (it works whether this option in dev toolbox is activated or not, so it's actually not a solution to this answer, wich is not an answer to the op question, sorry for that mess) – hugogogo Mar 29 '22 at 10:46
3

You can use SRI to break the browser cache. You only have to update your index.html file with the new SRI hash every time. When the browser loads the HTML and finds out the SRI hash on the HTML page didn't match that of the cached version of the resource, it will reload your resource from your servers. It also comes with a good side effect of bypassing cross-origin read blocking.

<script src="https://jessietessie.github.io/google-translate-token-generator/google_translate_token_generator.js" integrity="sha384-muTMBCWlaLhgTXLmflAEQVaaGwxYe1DYIf2fGdRkaAQeb4Usma/kqRWFWErr2BSi" crossorigin="anonymous"></script>
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Jessie Lesbian
  • 1,273
  • 10
  • 14
2

TomA's answer is right.

Using the "querystring" method will not be cached as quoted by Steve Souders below:

...that Squid, a popular proxy, doesn’t cache resources with a querystring.

TomA's suggestion of using style.TIMESTAMP.css is good, but MD5 would be much better as only when the contents were genuinely changed, the MD5 changes as well.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Tai Li
  • 51
  • 1
  • 1
  • 4
  • also, using a timestamp as a querystring param would force the reload of the file every single time, meaning no caching at all – Luca Oct 10 '12 at 23:40
  • 2
    A 2008 comment in that same blog post mentions that Squid's defaults have changed; the question is what percentage of your traffic is handled by (now) obsolete versions of Squid. – TomG Nov 06 '13 at 15:59
2

I suggest implementing the following process:

  • version your CSS and JavaScript files whenever you deploy. Something like: screen.1233.css (the number can be your SVN revision if you use a versioning system)

  • minify them to optimize loading times

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Dan
  • 9,912
  • 18
  • 49
  • 70
2

I see a problem with the approach of using a timestamp- or hash-based differentiator in the resource URL which gets stripped out on request at the server. The page that contains the link to e.g. the style sheet might get cached as well. So the cached page might request an older version of the style sheet, but it will be served the latest version, which might or might not work with the requesting page.

To fix this, you either have to guard the requesting page with a no-cache header or meta, to make sure it gets refreshed on every load. Or you have to maintain all versions of the style file that you ever deployed on the server, each as an individual file and with their differentiator intact so that the requesting page can get at the version of the style file it was designed for. In the latter case, you basically tie the versions of the HTML page and the style sheet together, which can be done statically and doesn't require any server logic.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
ThomasH
  • 22,276
  • 13
  • 61
  • 62
2

For a Java Servlet environment, you can look at the Jawr library. The features page explains how it handles caching:

Jawr will try its best to force your clients to cache the resources. If a browser asks if a file changed, a 304 (not modified) header is sent back with no content. On the other hand, with Jawr you will be 100% sure that new versions of your bundles are downloaded by all clients. Every URL to your resources will include an automatically generated, content-based prefix that changes automatically whenever a resource is updated. Once you deploy a new version, the URL to the bundle will change as well so it will be impossible that a client uses an older, cached version.

The library also does JavaScript and CSS minification, but you can turn that off if you don't want it.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
holmis83
  • 15,922
  • 5
  • 82
  • 83
2

A SilverStripe-specific answer worked out from reading: http://api.silverstripe.org/3.0/source-class-SS_Datetime.html#98-110:

Hopefully this will help someone using a SilverStripe template and trying to force reload a cached image on each page visit / refresh. In my case it is a GIF animation which only plays once and therefore did not replay after it was cached. In my template I simply added:

?$Now.Format(dmYHis)

to the end of the file path to create a unique time stamp and to force the browser to treat it as a new file.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
pinkp
  • 445
  • 2
  • 12
  • 30
2

Disable caching of script.js only for local development in pure JavaScript.

It injects a random script.js?wizardry=1231234 and blocks regular script.js:

<script type="text/javascript">
  if(document.location.href.indexOf('localhost') !== -1) {
    const scr = document.createElement('script');
    document.setAttribute('type', 'text/javascript');
    document.setAttribute('src', 'scripts.js' + '?wizardry=' + Math.random());
    document.head.appendChild(scr);
    document.write('<script type="application/x-suppress">'); // prevent next script(from other SO answer)
  }
</script>

<script type="text/javascript" src="scripts.js">
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Pawel
  • 16,093
  • 5
  • 70
  • 73
2

One of the best and quickest approaches I know is to change the name of the folder where you have CSS or JavaScript files.

Or for developers: Change the name of your CSS and JavaScript files something like versions.

<link rel="stylesheet" href="cssfolder/somecssfile-ver-1.css"/>

Do the same for your JavaScript files.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Bangash
  • 117
  • 9
2

I put an MD5 hash of the file's contents in its URL. That way I can set a very long expiration date, and don't have to worry about users having old JS or CSS.

I also calculate this once per file at runtime (or on file system changes) so there's nothing funny to do at design time or during the build process.

If you're using ASP.NET MVC then you can check out the code in my other answer here.

Drew Noakes
  • 300,895
  • 165
  • 679
  • 742
1

"Another idea which 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."

<link rel="stylesheet" href="file.css?<?=hash_hmac('sha1', session_id(), md5_file("file.css")); ?>" />

Hashing the file means when it has changed, the query string will have changed. If it hasn't, it will remain the same. Each session forces a reload too.

Optionally, you can also use rewrites to cause the browser to think it's a new URI.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Scott Arciszewski
  • 33,610
  • 16
  • 89
  • 206
1

Another suggestion for ASP.NET websites,

  1. Set different cache-control:max-age values, for different static files.

  2. For CSS and JavaScript files, the chances of modifying these files on server is high, so set a minimal cache-control:max-age value of 1 or 2 minutes or something that meets your need.

  3. For images, set a far date as the cache-control:max-age value, say 360 days.

  4. By doing so, when we make the first request, all static contents are downloaded to client machine with a 200-OK response.

  5. On subsequent requests and after two minutes, we see 304-Not Modified requests on CSS and JavaScript files which avoids us from CSS and JavaScript versioning.

  6. Image files will not be requested as they will be used from cached memory till the cache expires.

  7. By using the below web.config configurations, we can achieve the above described behavior,

    <system.webServer>
        <modules runAllManagedModulesForAllRequests="true"/>
        <staticContent>
            <clientCache cacheControlMode="UseMaxAge" cacheControlMaxAge="00.00:01:00"/>
        </staticContent>
        <httpProtocol>
            <customHeaders>
                <add name="ETAG" value=""/>
            </customHeaders>
        </httpProtocol>
    </system.webServer>
    
    <location path="Images">
        <system.webServer>
            <staticContent>
                <clientCache cacheControlMode="UseMaxAge" cacheControlMaxAge="180.00:00:00" />
            </staticContent>
        </system.webServer>
    </location>
    
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
1

If you are using a modern browser, you could use a manifest file to inform the browsers which files need to be updated. This requires no headers, no versions in URLs, etc.

For more details, see: Using the application cache

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Jos
  • 419
  • 3
  • 12
1

Many answers here advocate adding a timestamp to the URL. Unless you are modifying your production files directly, the file's timestamp is not likely to reflect the time when a file was changed. In most cases this will cause the URL to change more frequently than the file itself. This is why you should use a fast hash of the file's contents such as MD5 as levik and others have suggested.

Keep in mind that the value should be calculated once at build or run, rather than each time the file is requested.

As an example, here's a simple bash script that reads a list of filenames from standard input and writes a JSON file containing hashes to standard output:

#!/bin/bash
# Create a JSON map from filenames to MD5 hashes
# Run as hashes.sh < inputfile.list > outputfile.json

echo "{"
delim=""
while read l; do
    echo "$delim\"$l\": \"`md5 -q $l`\""
    delim=","
done
echo "}"

This file could then be loaded at server startup and referenced instead of reading the file system.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
undefined
  • 6,208
  • 3
  • 49
  • 59
1

I came to this question when looking for a solution for my SPA, which only has a single index.html file listing all the necessary files. While I got some leads that helped me, I could not find a quick-and-easy solution.

In the end, I wrote a quick page (including all of the code) necessary to autoversion an HTML/JavaScript index.html file as part of the publishing process. It works perfectly and only updates new files based on date last modified.

You can see my post at Autoversion your SPA index.html. There is a stand-alone Windows application there too.

The guts of the code is:

private void ParseIndex(string inFile, string addPath, string outFile)
{
    string path = Path.GetDirectoryName(inFile);
    HtmlAgilityPack.HtmlDocument document = new HtmlAgilityPack.HtmlDocument();
    document.Load(inFile);

    foreach (HtmlNode link in document.DocumentNode.Descendants("script"))
    {
        if (link.Attributes["src"]!=null)
        {
            resetQueryString(path, addPath, link, "src");
        }
    }

    foreach (HtmlNode link in document.DocumentNode.Descendants("link"))
    {
        if (link.Attributes["href"] != null && link.Attributes["type"] != null)
        {
            if (link.Attributes["type"].Value == "text/css" || link.Attributes["type"].Value == "text/html")
            {
                resetQueryString(path, addPath, link, "href");
            }
        }
    }

    document.Save(outFile);
    MessageBox.Show("Your file has been processed.", "Autoversion complete");
}

private void resetQueryString(string path, string addPath, HtmlNode link, string attrType)
{
    string currFileName = link.Attributes[attrType].Value;

    string uripath = currFileName;
    if (currFileName.Contains('?'))
        uripath = currFileName.Substring(0, currFileName.IndexOf('?'));
    string baseFile = Path.Combine(path, uripath);
    if (!File.Exists(baseFile))
        baseFile = Path.Combine(addPath, uripath);
    if (!File.Exists(baseFile))
        return;
    DateTime lastModified = System.IO.File.GetLastWriteTime(baseFile);
    link.Attributes[attrType].Value = uripath + "?v=" + lastModified.ToString("yyyyMMddhhmm");
}
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
statler
  • 1,322
  • 2
  • 15
  • 24
1

Small improvement from existing answers...

Using a random number or session id would cause it to reload on each request. Ideally, we may need to change only if some code changes were done in any JavaScript or CSS file.

When using a common JSP file as a template to many other JSP and JavaScript files, add the below in a common JSP file

<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<c:set var = "version" scope = "application" value = "1.0.0" />

Now use the above variable in all locations as below in your JavaScript file inclusions.

<script src='<spring:url value="/js/myChangedFile.js?version=${version}"/>'></script>

Advantages:

  1. This approach will help you in changing version number at one location only.
  1. Maintaining a proper version number (usually build/release number) will help you to check/verify your code changes being deployed properly (from developer console of the browser).

Another useful tip:

If you are using the Chrome browser, you can disable caching when Dev Tools is open. In Chrome, hit F12F1 and scroll to SettingsPreferencesNetwork → *Disable caching (while DevTools is open)

Chrome DevTools

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Jajikanth pydimarla
  • 1,512
  • 13
  • 11
1

A simple solution for static files (just for development purposes) that adds a random version number to the script URI, using script tag injections

<script>
    var script = document.createElement('script');
    script.src = "js/app.js?v=" + Math.random();
    document.getElementsByTagName('head')[0].appendChild(script);
</script>
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
loretoparisi
  • 15,724
  • 11
  • 102
  • 146
1

In ASP.NET Core you could achieve this by adding 'asp-append-version':

<link rel="stylesheet" href="~/css/xxx.css" asp-append-version="true" />

 <script src="~/js/xxx.js" asp-append-version="true"></script>

It will generate HTML:

<link rel="stylesheet" href="/css/xxx.css?v=rwgRWCjxemznsx7wgNx5PbMO1EictA4Dd0SjiW0S90g" />

The framework will generate a new version number every time you update the file.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Jayee
  • 542
  • 5
  • 15
1

If you don't want the client to cache the file ever, this solution seems to be quickest to implement. Adjust the part with time() if you e.g. load the file in footer.php:

<script src="<?php echo get_template_directory_uri(); ?>/js/main.js?v=<?= time() ?>"></script>
zyrup
  • 691
  • 2
  • 10
  • 18
0

My method to do this is simply to have the link element into a server-side include:

<!--#include virtual="/includes/css-element.txt"-->

where the contents of css-element.txt is

<link rel="stylesheet" href="mycss.css"/>

so the day you want to link to my-new-css.css or whatever, you just change the include.

AmbroseChapel
  • 11,957
  • 7
  • 46
  • 68
0

Well, I have made it work my way by changing the JavaScript file version each time the page loads by adding a random number to JavaScript file version as follows:

// Add it to the top of the page
<?php
    srand();
    $random_number = rand();
?>

Then apply the random number to the JavaScript version as follow:

<script src="file.js?version=<?php echo $random_number;?>"></script>
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Mizo Games
  • 189
  • 2
  • 8
  • 6
    That's a pretty bad idea. This means that the user has to re-download the file on every single page load. Caching is a good thing--this question is about how to utilize caching when you want to, but not when you don't. – Kip Aug 22 '16 at 01:20
  • Yea, the user has to re-download the file on every single page load because some java script included under (iframe) doesn't update page content unless the user press F5 or reload the page manually. So this is the best solution to reload the content each time when a new visitor visiting the website. – Mizo Games Aug 23 '16 at 21:47
0

We have one solution with some different way for implementation. We use the above solution for it.

datatables?v=1

We can handle the version of the file. It means that every time that we change our file, change the version of it too. But it's not a suitable way.

Another way used a GUID. It wasn't suitable either, because each time it fetches the file and doesn't use from the browser cache.

datatables?v=Guid.NewGuid()

The last way that is the best way is:

When a file change occurs, change the version too. Check the follow code:

<script src="~/scripts/main.js?v=@File.GetLastWriteTime(Server.MapPath("/scripts/main.js")).ToString("yyyyMMddHHmmss")"></script>

By this way, when you change the file, LastWriteTime change too, so the version of the file will change and in the next when you open the browser, it detects a new file and fetch it.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
0

Here is my Bash script-based cache busting solution:

  1. I assume you have CSS and JavaScript files referenced in your index.html file
  2. Add a timestamp as a parameter for .js and .css in index.html as below (one time only)
  3. Create a timestamp.txt file with the above timestamp.
  4. After any update to .css or .js file, just run the below .sh script

Sample index.html entries for .js and .css with a timestamp:

<link rel="stylesheet" href="bla_bla.css?v=my_timestamp">
<script src="scripts/bla_bla.js?v=my_timestamp"></script>

File timestamp.txt should only contain same timestamp 'my_timestamp' (will be searched for and replaced by script later on)

Finally here is the script (let's call it cache_buster.sh :D)

old_timestamp=$(cat timestamp.txt)
current_timestamp=$(date +%s)
sed -i -e "s/$old_timestamp/$current_timestamp/g" index.html
echo "$current_timestamp" >timestamp.txt

(Visual Studio Code users) you can put this script in a hook, so it gets called each time a file is saved in your workspace.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Amr Lotfy
  • 2,937
  • 5
  • 36
  • 56
0

I've solved this issue by using ETag:

ETag or entity tag is part of HTTP, the protocol for the World Wide Web. It is one of several mechanisms that HTTP provides for Web cache validation, which allows a client to make conditional requests. This allows caches to be more efficient and saves bandwidth, as a Web server does not need to send a full response if the content has not changed. ETags can also be used for optimistic concurrency control,1 as a way to help prevent simultaneous updates of a resource from overwriting each other.

  • I am running a Single-Page Application (written in Vue.JS).
  • The output of the application is built by npm, and is stored as dist folder (the important file is: dist/static/js/app.my_rand.js)
  • Nginx is responsible of serving the content in this dist folder, and it generates a new Etag value, which is some kind of a fingerprint, based on the modification time and the content of the dist folder. Thus when the resource changes, a new Etag value is generated.
  • When the browser requests the resource, a comparison between the request headers and the stored Etag, can determine if the two representations of the resource are the same, and could be served from cache or a new response with a new Etag needs to be served.
raul7
  • 171
  • 1
  • 10
0
location.reload(true)

Or use "Network" from the inspector ([CTRL] + [I]), click "disable cache", click trash icon, click "load"/"get"

-1

If you are using jQuery, there is an option called cache that will append a random number.

This is not a complete answer I know, but it might save you some time.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
roundcrisis
  • 17,276
  • 14
  • 60
  • 92
-2

Another way for JavaScript files would be to use the jQuery $.getScript in conjunction with $.ajaxSetup option cache: false.

Instead of:

<script src="scripts/app.js"></script>

You can use:

$.ajaxSetup({
  cache: false
});

$.getScript('scripts/app.js'); // GET scripts/app.js?_1391722802668
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Alex White
  • 802
  • 2
  • 9
  • 18
  • the idea isn't to prevent caching altogether. you just want the user to get the latest file as soon as the file is modified. – Kip Feb 06 '14 at 22:18
-3

Changing the filename will work. But that's not usually the simplest solution.

An HTTP cache-control header of 'no-cache' doesn't always work, as you've noticed. The HTTP 1.1 spec allows wiggle-room for user-agents to decide whether or not to request a new copy. (It's non-intuitive if you just look at the names of the directives. Go read the actual HTTP 1.1 spec for cache... it makes a little more sense in context.)

In a nutshell, if you want iron-tight cache-control use

Cache-Control: no-cache, no-store, must-revalidate

in your response headers.

pcorcoran
  • 7,894
  • 6
  • 28
  • 26
  • 1
    Problem with this approach is that it generates a round trip to the server for all such content. This is not good. – AnthonyWJones Sep 23 '08 at 08:14
  • This solution isn't perfect but it works for all situations, including static web pages. And if you are only doing this for a limited number of files, say your CSS files, then it shouldn't add a significant amount of time to the page load. – Bill Oct 05 '08 at 15:16
  • The first sentence may refer to [da5id's's deleted answer](https://stackoverflow.com/questions/118884/how-to-force-the-browser-to-reload-cached-css-and-javascript-files/118901#118901) -*"If an update is big/important enough I generally change the name of the file."*. – Peter Mortensen Nov 28 '20 at 03:50
-3

The simplest method is to take advantage of the PHP file read functionality. Just have the PHP echo the contents of the file into tags.

<?php
//Replace the 'style.css' with the link to the stylesheet.
echo "<style type='text/css'>".file_get_contents('style.css')."</style>";
?>

If you're using something besides PHP, there are some variations depending on the language, but almost all languages have a way to print the contents of a file. Put it in the right location (in the section), and that way, you don't have to rely on the browser.

  • 7
    Problem with this is you lose the ability to cache the file, making the experience slower for the user. – Austin Nov 03 '11 at 18:10