29

As per Yahoo's much-ballyhooed Best Practices for Speeding Up Your Site, we serve up static content from a CDN using far-future cache expiration headers. Of course, we need to occasionally update these "static" files, so we currently add an infix version as part of the filename (based on the SHA1 sum of the file contents). Thus:

styles.min.css

Becomes:

styles.min.abcd1234.css

However, managing the versioned files can become tedious, and I was wondering if a GET argument notation might be cleaner and better:

styles.min.css?v=abcd1234

Which do you use, and why? Are there browser- or proxy/cache-related considerations that I should consider?

3
  • The reason I ask is that I seem to recall hearing a reason NOT to use the GET-argument style, but I can't remember why.
    – David Eyk
    Nov 5, 2010 at 13:44
  • Doesn't using the GET-argument require serving the stylesheet with some kind of server side script (And it would no longer be static)? Nov 5, 2010 at 20:43
  • @Lotus: You can send the GET-arguments and they will be silently ignored if nothing is looking for them.
    – David Eyk
    Nov 8, 2010 at 14:20

4 Answers 4

16

Using the GET-style versioning, from a blank cache multiple URLs - e.g. style.css?v=123 and style.css?v=456 - would return the same content. However I can't see this would be problematic, especially since you'd only link to one at a time.

I think you'll find the GET-style much easier to maintain. You don't need separate files: just change the URL and browsers will fetch the CSS afresh.

UPDATE: on further research it appears that using a query string may stop browsers caching the files. However, if you are returning proper headers such as Expires this is not an issue.

UPDATE 2: the accepted answer points out that some proxies do not cache files with a query string. However this is based on old information; the particular issue they mention in Squid was fixed 7 years ago. Impressive Webs did a good write up on this.

4
  • That's the caveat I was trying to remember. Thanks for the link.
    – David Eyk
    Nov 8, 2010 at 14:19
  • One way query strings are problematic: on the server, there's only one file, but any query string is a different URL. You use versioning when you're using caches, and since each new version is supposed to be immutable, you will use extremely long caches. If you're using a reverse proxy, it's possible (albeit unlikely) for someone to attack your server by GET'ng a version you haven't created yet. E.g. you only have style.css?v=3, but someone GET's style.css?v=4. Now that file is cached in the reverse proxy. When you upload the 4th version, the proxy still serves the 3rd one it cache previously. Jun 1, 2020 at 19:39
  • @OdraEncoded You could always use the file hash as the query string, then it's not possible to guess what the next version will be. Or expire the new query string during the build process. Jun 2, 2020 at 11:24
  • If you use hashes it's probably never going to happen, but not mathematically impossible, and invalidating the reverse proxy caches adds complexity to the project. Imho, the safest way to do this is simply returning 404 when a non-existent version is requested. There's no reason for the latest version stored in the server to be returned in deployment (though maybe it makes sense in a development server), so the simplest solution is to use versions in filenames rather than in query strings. Jun 3, 2020 at 17:03
11

According to Google's Make the Web Faster, pages with query parameters are not cached by many HTTP proxies.

Most proxies, most notably Squid up through version 3.0, do not cache resources with a "?" in their URL even if a Cache-control: public header is present in the response. To enable proxy caching for these resources, remove query strings from references to static resources, and instead encode the parameters into the file names themselves.

So styles.min.abcd1234.css is the preferred solution. You can use an appropriate URL rewriting mechanism to turn styles.min.abcd1234.css into the easier to implement styles.min.css?v=abcd1234 transparently.

If you support HTTPS only, that advice does not apply, because proxies can't normally cache pages that are served over SSL.

1
  • 3
    I wonder if the information regarding query string caching and proxy servers is a bit dated? Google's docs no longer reference query strings and proxy servers in this context. Although the examples do still involve changing the filename itself. Squid 2.7 (2008) and 3.1 (2010) reportedly support query string caching by default, and earlier versions could be configured to support this.
    – MrWhite
    Oct 25, 2015 at 0:32
1

Both will work equally well as a query string is considered part of the URL and by changing it you are in effect changing the name of the resource thus causing the browser to fetch a new copy of the file.

I say use whichever method is easier for you to maintain.

0

However, managing the versioned files can become tedious, and I was wondering if a GET argument notation might be cleaner and better

You can limit the versioned filename to the frontend/HTML (eg. styles.min.abcd1234.css) and maintain just the static file (eg. styles.min.css) in the backend without any version string, by using URL-rewriting to rewrite the versioned filename to the backend file.

For example, on Apache you can use mod_rewrite:

RewriteEngine On

# Rewrite "styles.min.<hash>.css" to "styles.min.css"
RewriteRule ^styles\.min\.\w+\.css$ styles.min.css [L]

The version string does not need to be passed through to the backend (as a query string), since this is only used for cache-busting in the frontend.

Or, more generally, for any .css or .js file that contains a lowercase hexadecimal hash (of at least 16 chars) before the file extension:

# Rewrite "<something>.<hash>.(css|js)" to "<something>.(css|js)"
RewriteRule (.+)\.[a-f\d]{16,}\.(css|js)$ $1.$2 [L]

You can apply a similar technique to the .min (minified) file. ie. Reference styles.<version>.css in the frontend, but rewrite to styles.min.css in the backend (if it exists), otherwise fallback to styles.css (in development).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.