Fetching Stale Resources on Legacy Browsers? Try Busting This Trick Out

January 17, 2020
By Simon Proudfoot,
Front-End Developer

Sometimes, life gives you lemons. Unfortunately for developers, a lot of times those lemons are usually in the form of Internet Explorer 11 (IE11) bugs. While we can’t turn Internet Explorer into lemonade (get on it, science!), what we can do is use it as an opportunity to learn something new. In my case, I came across a lemon in the form of a misbehaved modal on one of our projects that just wouldn’t behave as expected, leaving everyone on the team befuddled.

After going through the initial steps of my typical bug-fixing process (e.g.: keyboard mashing, contemplate changing identities, wondering how much money I could get from selling all my possessions and moving to the woods, etc.), I started combing through the dev tools. After some further testing and messing around, I found that one of our calls to our resource was returning stale and outdated data. This seemed odd, to say the least.

So I now understood what the problem was, but I still didn’t understand the nature of the problem. The difference between the two is like understanding how to put out a fire, as opposed to understanding why the fire happened in the first place.

My go-to place to check while debugging for these kinds of things, the console, proved to be fairly fruitless, and nothing jumped out at me while looking at the markup. With the basics out of the way, I ventured over to the Network tab to see if there was any smoking gun I could point to. At first, everything seemed fairly normal. Again, seemed. 

However, I noticed that the response code on one of our resources was 304, instead of the 200 I was expecting. To be honest, I wasn’t quite sure what the 304 response was at the time, but I knew that it wasn’t a 400, so I knew that our call to our resource wasn’t failing. 

A brief look through MDN led me to understand that a 304 response is a “client redirection response code indicates that there is no need to retransmit the requested resources. It is an implicit redirection to a cached resource.” With this information, things started to add up.

A Brief Interlude to Talk About Cache

Before we go any further, this seems like a good point to start talking about caching. Caching is one of those weird things that’s great, until it isn’t. What do I mean by that? Well, first let’s understand what caching is.

Caching is essentially the practice of storing a local copy of a resource that gets served up when requested. There are all kinds of different caches, but at its most basic level, that’s what it all boils down to. For things that are typically used across a site, like a CSS file, for example, this is a very useful technique in expediting page loads by decreasing the number of resources needed to download.

Let’s imagine for a minute that we’re making two batches of cookies, one chocolate chip, and the other oatmeal-raisin (yeah, I’m that guy). There are several ingredients that are common between them: butter, sugar, flour, eggs, baking powder, baking soda, etc. For those things, it would be a lot more efficient to keep the ingredients that are common between the two batches of cookies on the counter rather than going back to the cupboard both times to get the same ingredients, right? 

In this case, the batches of cookies are the pages of our website, the ingredients are our resources, and caching is storing our common ingredients on the counter once we get them from the cupboard. I’m sure you can see why this is much more efficient across the two batches of cookies, and how that efficiency would become much more pronounced with every batch of cookies we added. On the web, our sites typically have more than two pages, so caching becomes a pretty essential way of keeping our sites movin’ and groovin’.

Back to Our Original Problem

So, I just spent a moment waxing poetic about caching, but of course, there are times when caching can come back to bite you. This, my friends, was one of those times.

Remember how I was saying earlier that we were getting stale data? Well, I’m sure you can see where this is going, but this is where it all comes together. You see, I realized that our browser was trying to do us a solid by requesting the same resource as few times as possible.

Under ordinary circumstances, this would be great. However, these were not exactly ordinary circumstances. For our purposes, we actually needed the browser to request the same resource on a regular basis since it was being updated by the user very frequently as they used our app.

So how do we go about telling our browser that we need it to grab the latest version of a given resource? Well, friends, let me tell you about a little something called cache busting

In The Beginning, There Was File Revving…

You see, my problem was not something new to the web. For a long, long time, fetching the latest version of a resource has been a problem that has been a bit of a pain point. The reason was largely the same — meaning, the browser was caching the resource, thus always serving up an outdated version of it whenever refreshing the page. The solution, for most purposes, was a practice called “file revving.” What it ended up looking like was instead of having this:

<link href="mysite.css" rel="stylesheet" type="text/css">

We’d have something like this:

<link href="mysite.v10.css" rel="stylesheet" type="text/css">

The mechanism that makes this work is really just that .v10 portion of the second example — and to be clear, it could really be any text we want. The idea behind file revving was that with each incremental revision to the file, the file name would be updated with a corresponding version number. 

The result was that the browser would essentially be tricked into grabbing a new version of the resource until it was updated again. As far as the browser knows, mysite.v10.css and mysite.v11.css are technically different files, and therefore serving up the cached version of the file is moot.

Sorry, But Our Princess Is In Another Castle

So file revving must be the answer, right? Well, not so fast there, compadre (I know, this heartbreak is killing me too).

You’ll notice in my examples above, that those were all internal resources that we were planning on serving up. However, the bug that I was dealing with wasn’t exactly of that nature. Our use case was that we had a resource that we were making a request to and expecting a response from, which we were then using the data from that response to show some information on the screen. Not exactly the place for file revving, but at least we’re on the path now!

So, with our backs seemingly at the wall here, where do we turn? Well, we’re still going to use a cache-busting technique, although this time will be a little different. This time, we’re using a query string for the same purpose. Thankfully, this doesn’t require too much of a chance on our part. 

For example, ordinarily, you’d make a request like this:

fetch(`/myresource.js`)
.then(response => {
	// Get down with your bad self here
});

And now, we’ll just make a small tweak like this…

fetch(`/myresource.js?cachebust=${Date.now()}`)
.then(response => {
		// Get down with your bad self here
});

All we’re really doing is appending the query string cachebust=${Date.now()}, where the Date.now() method returns the number of milliseconds elapsed since January 1, 1970 (source). Thanks to a little ES6 string interpolation (remember to use back-ticks!), that will give you a new value every time you fetch that resource, once again tricking the browser into thinking that you’re requesting a new version of that resource each time. Again, as far as the browser knows, /myresource.js?cachebust=1566328175263 and /myresource.js?cachebust=1566328187596 are two different resources, and a separate request should be made for each. Problem solved, no more IE11 heartache.

A small Word Of Caution

I’d be remiss not to acknowledge this article from Steve Souders, in which he explicitly (as in, literally right at the top of the post) says to use file revving instead of query strings to cache-bust. While I’m sure that this was perfectly practical advice at the time, the use case described in that post is slightly different from the particular problem we are tackling here, and doesn’t necessarily apply in the same way.

Furthermore, the primary reason listed in the article (written in 2008 — for historical context, HTML5 did not exist at this time) for using file revving as opposed to query strings was that Squid, a popular proxy, does not cache files with a query string. Squid, as of v2.7, now allows for caching of files with query strings.

Need more proof? See this exchange on Twitter below where Ilya Grigorik (from his own Twitter bio: “Web performance engineer at Google; co-chair of W3C Webperf WG. In short, an internet plumber.”) chimes in to say that he hasn’t seen a problem with query strings in the last 5+ years.