27

I'm trying to figure out how to properly handle the web browser cache for single page apps.

I have a fairly typical design: several HTML, JS and CSS files implementing the SPA, and a bunch of JSON data that's consumed by the SPA. Problems arise when I want to push an update: I update the static portion of the site and the code that generates the JSON at the same time, but client browsers often have the static portion cached, so old code tries to process the new data and may (depending on changes made) run into problems. (In particular, IE seems more aggressive than Chrome or Firefox about using cached JS without revalidating.)

What's the best way to handle this?

  1. Make sure my JSON changes are backwards-compatible, and assume browser caches will expire in a reasonable timeframe.
  2. Embed some sort of version number in both the static JS and the JSON, then execute window.location.reload(true); if they don't match.
  3. Figure out the appropriate combination of headers (must-revalidate or no-cache or whatever; sources vary on how to do this) to ensure that browsers always revalidate all resources on every load, even if it means a few extra round trips to load the site.
  4. Micro-manage my cache-control and expires headers so that static content expires when I want to push an update.
  5. Something else?
  • 1
    Have heard #3 and #4 unexpectedly fail in embedded web browser controls if your environment is evil (cough iOS) from coworkers. #1 and #2 can be an app level choice but still might(?) cause cache issues for other resources or for partial resource loads. The only thing I've seen reliably work in production-ready code is fetching yoururl.html?<SomeTimeStamp> as this will fake out most caching mechanisms. Bonus: roll your whole web app into a file so that loads either atomically succeed or fail. Downside: works best on a local link. Your mileage may vary. Good luck! – J Trana Sep 16 '14 at 4:26
  • 2
    +1 to using a version number or a timestamp as a URL parameter for resources. – 9000 Sep 16 '14 at 4:52
14

You need a cache busting solution. The role of cache busting is:

  1. Rename resources to a unique name depending on their content.
  2. Update all references to those resources.

In a Grunt-based project it's common to use grunt-rev to ensure that all files that need to be refreshed are given unique names, based on their content.

If you ensure that your JSON files get cachebusting filenames along with the references to them in your Javascript, clients will always load the JSON files that the Javascript expects.

The advantage of hash-based file naming is that files that have not changed will get the same filenames after cache-busting, so browsers can continue to safely use cached content when it has not changed.

Obviously this is the kind of thing you want to be automated as part of your project's production build so you don't have to keep track of changing file names & references manually.

  • 2
    +1 for the italicized "cache busting" bit, which opens the door to actually google this stuff productively. – Zak Kus Dec 12 '14 at 2:45
  • @Ted Percival - yeoman framework does this, which i use, but im seeing a problem. when i release a new build, the browser may have the index.html cached with references to the old files... and the browser gets an error. how should i fix this? (A.) symlink all the old file names to the new ones (this works) (B.) add no-cache header to index.html (but is this always respected) (C.) add .htaccess to recognize a revved file and search for the base one (ie. 12345.main.js --> main.js ) – timh Feb 24 '15 at 13:34
5

You can use if-modified-since + last-modified or if-none-match + etag headers along with the proper cache-control header. (There can be browser bugs, but nothing you cannot manage in recent browsers.)

If the files are static, then I suggest you to use if-modified-since, because it can be done automatically with a well configured HTTP server. It should send back 304 if the file is not modified since the last download.

I don't think your #1 and #2 would work in long term. The #3 or #4 can work. The #3 is simpler, but you have to learn how to deal with this problem only once. So I'd try the #4 if I were you, but the solution might depend on what browsers your customers use... For example IE8 has problems by updating ajax cache, etc...

2

If you can include Java Servlet Filter in your SPA, here is a working solution: CorrectBrowserCacheHandlerFilter.java

Basically, when your browser requests the static files, the server will redirect every requests to the same one but with a hash query parameter (?v=azErT for example) which depends on the content of the target static file.

Doing this, the browser will never cache the static files declared in your index.htmlfor example (because will always received a 302 Moved Temporarily), but will only cache the ones with the hash version (the server will answer 200 for them). So the browser cache will be used efficiently for those static files with hash version.

Disclaimer: I'm the author of CorrectBrowserCacheHandlerFilter.java.

protected by gnat Apr 11 '18 at 15:42

Thank you for your interest in this question. Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).

Would you like to answer one of these unanswered questions instead?

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