Cache Busting using Javascript
Problem
Google Chrome (and possibly, other browsers) don’t properly honor the Cache-Control header.
If you are on Page A, click a link for Page B, then hit the back button to go back to Page A, Chrome uses a pre-rendered, cached version of Page A, which may now have stale content.
Action | Location |
---|---|
Start | Page A 8:05 AM |
Click link on Page A | Page B 8:10 AM |
Hit browser back button | Page A 8:05 AM |
At this point, Page A is basically static. Any dynamic content won’t be refreshed, even if it executes in Javascript.
Disrupting this behavior is known as “cache busting”.
Solution
Add this Javascript to your page’s <HEAD> section:
<SCRIPT> function nocache(){ var tc3=new Date(); var delta=tc3-tc1; if (delta>2000) { window.location.reload(); } else { tc1=tc3; } } var tc1=new Date(); setInterval(nocache,1000); </SCRIPT>
How it Works
When the page first executes, it creates a Javascript variable tc1 with a current timestamp.
Then, it sets up a timer callback function, nocache(), which executes every 1 second.
When nocache executes, it creates a new timestamp (tc3), and if the difference between the two is greater than 2 seconds, it reloads the current page. If not, it updates the tc1 timestamp to current.
The result is that if the page remains static for more than 2 seconds, it will refresh. However, as long as the page is active, it keeps updating the timestamp in tc1 instead.
If you click the link to Page B, wait 2 or more seconds, then hit the browser’s back button, the timestamp in tc1 will be the point in time from when the link was clicked (more than 2 seconds ago). One second after you hit the back button, the nocache function sees that tc1 is more than 2 seconds out of date and reloads the page. If the page successfully reloads, tc1 is updated in the process. If not, it simply retries the reload every 1 second.
Also, if your page uses JSON or XML to dynamically render content, the nocache timer function can simply call your page’s update function rather than reload the entire page.
Conclusion
When you hit the browser’s back button, Chrome tries very hard to use a pre-rendered page from cache rather than use the Cache-Control header or contact the server to see if the page is stale (which it should be doing). This means that when you hit the back button, any dynamic code has already executed, and the page picks up where it left off from a script execution standpoint. Unfortunately, this includes any events that have fired, which means that the pre-rendered version of the page is basically static.
I have seen other solutions that use the onpageshow event, or other event hooks. However, I have found these to be unreliable in Chrome for cache busting. In contrast, the timer hook must be honored for basic page functionality, and therefore can’t simply be killed off like other events.
A side effect of this approach is that if the server is unavailable and the page tries to refresh, it will get stuck in a refresh loop until the server is available again. This is not necessarily a negative behavior, but could easily be fine tuned using a counter, for example, to only try to refresh a set number of times and then stop. During page load, “retries” is set to some number, n. “Retries > 0” is then added as a condition to the if block, which then decrements retries during each execution. If retries hits zero, it updates tc1 to current, which stops all attempts. In another variation, retries can be its own, outer “if” block, so that some other exit action can be executed, for example, alerting the user that the server is offline and the page is stale.
A fleshed-out version would look like this:
<SCRIPT> function nocache() { var tc3=new Date(); var delta=tc3-tc1; if (retries>0) { // Normal action if (delta>2000) { retries-=1; window.location.reload(); } else { tc1=tc3; } } else { // When retries hits zero, set error text, make the element visible, and disable the nocache timer var e=document.getElementById("ErrorText"); e.innerHTML="The server is offline and this page is dead. Have a nice day."; e.style.visibility="visible"; clearInterval(nocachetimer); } } var tc1=new Date(); var retries=3; var nocachetimer=setInterval(nocache,1000); </SCRIPT> <DIV ID=ErrorText STYLE='visibility:hidden; display:block;'> </DIV>
I hope you find this useful, and thanks for reading!