143

I have been looking at a HTML 5 boilerplate template (from http://html5boilerplate.com/) and noticed the use of "?v=1" in URLs when referring to CSS and JavaScript files.

  1. What does appending "?v=1" to CSS and JavaScript URLs in link and script tags do?
  2. Not all JavaScript URLs have the "?v=1" (example from the sample below: js/modernizr-1.5.min.js). Is there a reason why this is the case?

Sample from their index.html:

<!-- CSS : implied media="all" -->
<link rel="stylesheet" href="css/style.css?v=1">

<!-- For the less-enabled mobile browsers like Opera Mini -->
<link rel="stylesheet" media="handheld" href="css/handheld.css?v=1">

<!-- All JavaScript at the bottom, except for Modernizr which enables HTML5 elements & feature detects -->
<script src="js/modernizr-1.5.min.js"></script>

<!------ Some lines removed ------>

<script src="js/plugins.js?v=1"></script>
<script src="js/script.js?v=1"></script>

<!--[if lt IE 7 ]>
  <script src="js/dd_belatedpng.js?v=1"></script>
<![endif]-->


<!-- yui profiler and profileviewer - remove for production -->
<script src="js/profiling/yahoo-profiling.min.js?v=1"></script>
<script src="js/profiling/config.js?v=1"></script>
<!-- end profiling code -->
Mario Petrovic
  • 4,128
  • 5
  • 26
  • 43
maxyfc
  • 10,507
  • 7
  • 34
  • 45

8 Answers8

183

These are usually to make sure that the browser gets a new version when the site gets updated with a new version, e.g. as part of our build process we'd have something like this:

/Resources/Combined.css?v=x.x.x.buildnumber

Since this changes with every new code push, the client's forced to grab a new version, just because of the querystring. Look at this page (at the time of this answer) for example:

<link ... href="http://sstatic.net/stackoverflow/all.css?v=c298c7f8233d">

I think instead of a revision number the SO team went with a file hash, which is an even better approach, even with a new release, the browsers only forced to grab a new version when the file actually changes.

Both of these approaches allow you to set the cache header to something ridiculously long, say 20 years...yet when it changes, you don't have to worry about that cache header, the browser sees a different querystring and treats it as a different, new file.

Nick Craver
  • 594,859
  • 130
  • 1,270
  • 1,139
  • Note that, of course, this hash (should it be based on the file’s contents) should not be re-calculated on each page request… ;) – scy Aug 12 '10 at 11:26
  • @Scytale - I would say that need not be said, *but* unfortunately I've seen a commercial document management system that did that before, on 5+ gig worth of files every time every client did a sync to compare the contents :( – Nick Craver Aug 12 '10 at 11:31
  • -1 for knowing about cache control headers, yet advertising these URL horrors. – Free Consulting Apr 08 '11 at 03:05
  • 3
    @Free - A cache control header sent *yesterday* can't tell the client the file changed today (the client won't even check), a URL can. Can you explain what I'm missing there? – Nick Craver Apr 08 '11 at 03:08
  • User? Server should not talk to user instead of his browser. You are missing a HTTP protocol and its fine grain control of telling UA when to ask for modified entity. Effectively allowing border proxy to perform a DoS on your web app, too :-) – Free Consulting Apr 08 '11 at 03:21
  • 9
    @Free - The way these files are cached is *forever*, meaning the client is in *no way* checking to see if the file is modified. This means they would *never* get the updated file...unless the URL changed, which is what happens with the technique above. You get maximum cache life on the client (fewest HTTP requests) but the client is instantly updated *when the file actually changes*. Exactly how would you accomplish all this using *only* cache control headers? – Nick Craver Apr 08 '11 at 03:24
  • When one ask a *compliant UA* to store entity forever, and when figure out what it has been expired and feel sudden desire for UA to ask if it has been modified, whose fault it is? Here we are, a kludge to support a previous kludge to support previous kludge. With reasonable entity age it works properly in the natural way. – Free Consulting Apr 08 '11 at 03:41
  • 1
    Hashing the file modification date is good enough for all practical purposes, and is (in my experience) an inexpensive operation even if done in real time. – Matt Sherman Apr 08 '11 at 03:46
  • 4
    @Free - Stack Overflow gets 5 million visitors a month, your approach would have 2 impacts: a) *many* more requests and data sent to/from our servers, and b) the users wouldn't *immediately* get new JavaScript/CSS (for example when we had a bug, or the HTML changes needing new JS/CSS). Natural expiration really isn't an option here. The method you're proposing would be technically much less efficient and result is *actual user bugs*, on a regular basis...that isn't really acceptable on any major site (nor should it be om *any* site really). – Nick Craver Apr 08 '11 at 03:47
  • a.1) not **many** more if configured properly a.2) requests ending in 304 do not produce substantial load and b) in HTTP nothing happens immediately, when you *break something in the production environment* caching users might not even notice, which works as *traffic shaper*. Anyway, 1.9 visitors per second × 7 requests per page (requisites) isnt really horrifying number, given how much work done client-side (yes, i'm talking about what given instead of custom tag feeds) – Free Consulting Apr 08 '11 at 04:24
  • 2
    @Free - The 5 million is 5 million [**visitors** per month](http://www.quantcast.com/p-c1rF4kxgLUzNc), since we deploy *many times a day*, the requests are many times that. In terms of HTML page-loads we're talking a little over 110 million per month (and growing...again that's *only* HTML page loads). For a) yes, many more, or more breaks, it's a trade-off either way on the cache time vs clients having correct content. Also, your logic for b) is flawed, the html is *not* cached, so used with cached JS that no longer works means *only* cached users are affected, not that they're immune. – Nick Craver Apr 08 '11 at 04:41
  • Yes, i have a calculator, estimated number requests which should be served by border cache and was not really horrified. b) implies what caches must be coherent, otherwise is it quite pointless. Time for me to cease trying to convince the convinced. – Free Consulting Apr 08 '11 at 05:14
  • 1
    @Nick, the code snippet in the original question was taken from [HTML5BP](http://html5boilerplate.com/); which has removed the query-string versioning in favor of filename revving due to some issues with proxy servers and caching ([learn more from Steve Souders](http://www.stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring/)). There is a little more work involved with filename revving since some URL rewriting needs to be set up. – David Murdoch Apr 08 '11 at 11:53
  • Also, not a big deal, but might be worth mentioning: by using hashes instead of file name *versions* you basically are giving the middle finger to your GZIP compression algo (you [should send RAW DEFLATE](http://stackoverflow.com/questions/1574168/deflate-compression-browser-compatibility-and-advantages-over-gzip) instead, btw) as it can't do crap to compress strings like `ef2f2383c884` and `c7993dfe1bc5`. Granted, the hashing technique is easier to implement that versioning. – David Murdoch Apr 08 '11 at 12:02
  • I remember that I used the hash way before, and the browser didn't get my last updated file, and as you may see in this screen shot http://screencast.com/t/tgNRhHMB I can't see where stackoverflow is using this hash, and Also I can't understand why hash way could be better then with the query string. – Amr Elgarhy May 05 '11 at 20:19
  • @David Murdoch: Regarding your last comment - what are you talking about? `c298c7f8233d` (the hash) is not *inside* the entity body (the thing that gets compressed). – thirtydot Aug 19 '11 at 09:33
  • @thirtydot, I think you may have overlooked the fact that the browser will have to download the hash when it downloads the HTML for the page. :-p – David Murdoch Aug 19 '11 at 11:13
  • @David Murdoch: Yeah, I totally overlooked that :) Time for critique Plan B: what difference does 12 bytes make in a multi kilobyte HTML page? Then again you did already say it's "not a big deal". I'm just going to shut up. – thirtydot Aug 19 '11 at 11:19
  • @David - Versions are much more complicated to implement with very little benefit and much more overhead to the entire build process. A hash is *very* simple, and therefore *very* easy to maintain (and with a CDN, makes life much simpler as well). We're talking a few bytes here for where the alternative is (comparatively) a world of hurt, which would you choose? Currently our hashing has *no* dependency on the build or what version of the code is deployed, it *only* needs the file contents to hash, which are readily available, you can see the obvious choice for us here. – Nick Craver Aug 19 '11 at 11:20
  • @Nick I agree. And admit that I'm a bit of a hypocrite since I don't use versions in my current MVC boilerplate setup (though I hash on LastModified instead of file contents) – David Murdoch Aug 19 '11 at 16:27
  • 1
    Hi, is "v" parameter fixed? is there any other parameters that I can find? – Sam YC Jul 02 '13 at 04:01
  • 5
    @GMsoF v just represents "version" for us, it's a completely arbitrary choice. Any value query string would work, e.g. it could just as easily be ?jejdutogjesudo= – Nick Craver Jul 02 '13 at 10:09
  • @NickCraver Will adding version number allow this file to be cached? `test.js?v=1` will request new file every time or only for initial time and then cache and use it unless url is changed? – Rajesh Oct 15 '15 at 10:34
  • @NickCraver where/how do you cache the file hashes? It seems kinda of time expensive. – Santhos Jan 25 '16 at 13:37
  • @Rajesh the hash needs to change or cache needs to expire...the point is you know when the contents change so change the querystring when that happens. – Nick Craver Jan 25 '16 at 14:02
  • 2
    @Santhos we use a simple dictionary that calculates if missing to get the hashes - so we're reading and hashing the file just once per app lifetime...it's really cheap. Since a build for almost all websites starts and stops *that specific app domain or process*, a simple in-memory cache works very well. When the files change, you're re-calcing anyway. – Nick Craver Jan 25 '16 at 14:03
  • 1
    9 years later, is this still applicable? Is there any official documentation on this feature? – cdalxndr Oct 10 '19 at 12:08
24

This makes sure you are getting the latest version from of the css or js file from the server.

And later you can append "?v=2" if you have a newer version and "?v=3", "?v=4" and so on.

Note that you can use any querystring, 'v' is not a must for example:

"?blah=1" will work as well.

And

"?xyz=1002" will work.

And this is a common technique because browsers are now caching js and css files better and longer.

Amr Elgarhy
  • 59,046
  • 67
  • 178
  • 291
13

The hash solution is nice but not really human readable when you want to know what version of file is sitting in your local web folder. The solution is to date/time stamp your version so you can easily compare it against your server file.

For example, if your .js or .css file is dated 2011-02-08 15:55:30 (last modification) then the version should equal to .js?v=20110208155530

Should be easy to read properties of any file in any language. In ASP.Net it's really easy...

".js?v=" + File.GetLastWriteTime(HttpContext.Current.Request.PhysicalApplicationPath + filename).ToString("yyMMddHHHmmss");

Of coz get it nicely refactored into properties/functions first and off you go. No more excuses.

Good luck, Art.

Chetan Gawai
  • 2,133
  • 18
  • 34
Art
  • 225
  • 3
  • 4
  • 2
    What if you are building your website only with html js and css . Then how can we automatically inject version name to static resources? – Nishanth Nair Jan 24 '14 at 11:52
  • @Whizkid747 late reply, but for newcomers, whatever site builder / build system you're using should have a way to get the date in milliseconds which you can use as the version, otherwise if you're not using a builder//build system you'd have to write these yourself. – AntK May 23 '17 at 12:31
7

In order to answer you questions;

"?v=1" this is written only beacuse to download a fresh copy of the css and js files instead of using from the cache of the browser.

If you mention this query string parameter at the end of your stylesheet or the js file then it forces the browser to download a new file, Due to which the recent changes in the .css and .js files are made effetive in your browser.

If you dont use this versioning then you may need to clear the cache of refresh the page in order to view the recent changes in those files.

Here is an article that explains this thing How and Why to make versioning of CSS and JS files

HoldOffHunger
  • 10,963
  • 6
  • 53
  • 100
Tapan kumar
  • 5,784
  • 1
  • 22
  • 21
7

Javascript files are often cached by the browser for a lot longer than you might expect.

This can often result in unexpected behaviour when you release a new version of your JS file.

Therefore, it is common practice to add a QueryString parameter to the URL for the javascript file. That way, the browser caches the Javascript file with v=1. When you release a new version of your javascript file you change the url's to v=2 and the browser will be forced to download a new copy.

Robin Day
  • 95,170
  • 23
  • 113
  • 161
2

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: <script src="js/excel.js?time=<%=new java.util.Date()%>"></script>

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.

// 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);
}
TylerH
  • 19,065
  • 49
  • 65
  • 86
0

As you can read before, the ?v=1 ensures that your browser gets the version 1 of the file. When you have a new version, you just have to append a different version number and the browser will forget about the old version and loads the new one.

There is a gulp plugin which takes care of version your files during the build phase, so you don't have to do it manually. It's handy and you can easily integrate it in you build process. Here's the link: gulp-annotate

TylerH
  • 19,065
  • 49
  • 65
  • 86
Phugo
  • 390
  • 1
  • 10
-2

As mentioned by others, this is used for front end cache busting. To implement this, I have personally find grunt-cache-bust npm package useful.

Ram
  • 71
  • 1
  • 6
  • 1
    While this link may answer the question, link only answers are discouraged on Stack Overflow, you can improve this answer by taking vital parts of the link and putting it into your answer, this makes sure your answer is still an answer if the link gets changed or removed :) – WhatsThePoint Jan 24 '18 at 18:34