Smart JavaScript Caching

Like many web-based software companies, a significant part of our frontend logic is dependent on JavaScript. This allows us to add all of the neat little animations and dynamic page updates that make web apps fun. It also dramatically increases the size of the JavaScript files that need to be downloaded.

Our approach in the past has been to just cache everything and forget about it. Unlike many public websites that have to operate under the assumption that the user's cache will be empty, we can depend on a fairly high percentage of populated caches. And, because we have a single page application design, even if they don't have the JavaScript cached, they only have to download the files once and they'll be available the entire time the application is being used.

Our problem has actually been that pages are cached too much. We update our software fairly often, and most of the time these changes affect the JavaScript. When someone tries to use the app with outdated files in their cache all kinds of annoying blowups happen. Development was also a pain because we had to constantly keep clearing our browser cache to test new code. It wasn't until recently, though, that we implemented a solution to these problems.

Step 1: Force Cache Reload After Upgrade The most serious of caching problems was that people would have to manually clear their browser cache after we upgraded the application. This was solved by dynamically sticking the application version number into the JavaScript file's querystring. Every time the app was upgraded the version number would change, as would the querystring. And, because the browser sees a new query string variable, it downloads the file from the server. The HTML being generated ended up looking like this:

<script type="text/javascript" src="js/dojo/dojo.js?redownloadToken=1.2.17"></script>

Step 2: Make the Developers' Lives Easier Developers are smart folks and can usually figure out when cache needs clearing. Just because they can, however, doesn't mean they should have to. To remedy this problem we added a "development mode" to the app. When enabled, the redownloadToken above is changed from the application's version number to a timestamp, effectively forcing a cache-free download on every page refresh. Even better, when development mode is enabled the app automatically uses the uncompressed dojo build for maximum debugability. Here's the JSTL code we used:

<c:when test="${development}">
<script type="text/javascript" src="<c:out value='${baseUrl}'/>/js/dojo/dojo.js.uncompressed.js?redownloadToken=<c:out value='${redownloadToken}'/>"></script>
<script type="text/javascript" src="<c:out value='${baseUrl}'/>/js/dojo/dojo.js?redownloadToken=<c:out value='${redownloadToken}'/>"></script>

In the end we haven't solved all caching problems, but our customers and developers are sure happier!