Solution 1 :

A very inelegant workaround is to use a CSS animation that makes an “offline” element appear all at once after a set amount of time. With JavaScript, you can continually reset the animation, preventing it from ever showing unless/until JavaScript is no longer running to reset it.

The animation in the example below is set to 3 seconds, and uses keyframes to stay at opacity:0 until 99% of the animation duration, at which point it changes to opacity:1.

In JavaScript we can reset the animation every 1-2 seconds with JavaScript by removing the class that is associated with it, forcing a reflow (by fetching the offsetWidth, for instance), and then adding the animation class back.

Ideally we could tell Chrome on these mobile devices to not cache the page at all. The various headers we tried in the comments on the question were not respected by a mobile device running with no Internet access.

Disabling the caching would be a more appropriate solution in both concept and execution. Having a running JavaScript interval that is perpetually forcing a reflow isn’t great. But, it does give the user an indication that the JavaScript engine has stopped running, which is what the situation is here.

In the example below, the “Simulate stop JS” button just clears the interval that is continuing to reset the loading animation. This is a simulation only, but has the same effect as JavaScript not running (tested on an isolated server).

const overlay = document.getElementById('offline-overlay');

const stopJS =  document.getElementById('stopJS');

const heartbeat = () => {
  void overlay.offsetWidth; 

const heartbeatInterval = setInterval( heartbeat, 1000);

stopJS.addEventListener("click", function(){
@keyframes offline {
  0%   {opacity:0;}
  99%  {opacity:0;}
  100% {opacity:1;}

  animation-name: offline;
  animation-duration: 3s;

  background-color: rgba(255,255,255,.9);
  justify-content: center;

#offline-overlay span{
  font-family: sans-serif;
  color: #888;
  <div id="offline-overlay" class="animate-overlay">

<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam varius quam sed nulla feugiat varius. Praesent vitae mi et libero porttitor maximus. Suspendisse eu pulvinar quam. Phasellus id ante a elit faucibus cursus. Curabitur porttitor vehicula ornare. Suspendisse nec risus ex. Aenean bibendum auctor ex eget aliquet. Donec laoreet sem ut tortor viverra aliquam.</p>

<button id="stopJS">Simulate Stop JS</button>

Problem :

Steps to reproduce the issue:

  1. The user visits the webpage, see the code below.
  2. The user closes Chrome.
  3. The device goes completely offline (turn all networking off manually).
  4. The user re-opens the browser while completely offline.
  5. Chrome automatically serves the last visited page, a saved copy of the webpage which says Online? true, even after hitting refresh several times.

The only thing that tells the user that she/he is looking at some stale, completely unusable copy of the web page is this in the address bar:

enter image description here

Non-technical users can easily miss that, and are left wondering why the page is unusable… This is bad user experience.

Browser & device: Chrome 81 on Android 6 on an Acer Iconia Tab 10 A3-40 tablet.

The webpage is served over HTTPS (secure connection).


const setMsg = (flag) => {
  const p = document.getElementById('msg')
  p.innerHTML = '<b>Online?</b>  ' + flag


window.addEventListener("online", () => {
window.addEventListener("offline", () => {
<p id='msg'> </p>

As far as I can tell:

  • Chrome does not re-run any JavaScript in Step 5, even after hitting refresh.
  • Chrome does not respect the Cache-Control: private, no-store either; double-checked.

So far, the only way I could prevent this from happening is to register a service worker. When I have a service worker registered, the JavaScript is re-run and I can properly and clearly inform the user that she/he is offline.

Without a service worker, how can I prevent Chrome from loading a stale, unusable webpage when offline?

The usual “No internet” page with the dinosaur is appropriate, and that’s what I was expecting with Cache-Control: no-store.


Comment posted by tmdesigned

I dont have time to research fully right now, but have you tried adding another listener for ‘pageshow’ that manually runs the check? That’s how you rerun JS for a back button navigation, for instance, which seems like a similar problem.

Comment posted by Ali

@tmdesigned No,

Comment posted by tmdesigned

On an existing site of mine, I have cache-control: no-cache, no-store, must-revalidate and it doesn’t restore the page. Still, I would have thought no-store alone would do that.

Comment posted by Ali

@tmdesigned I have bad news for you: This version of Chrome

Comment posted by tmdesigned

Terrible, awful, no-good CSS based workaround. Have an ‘online’ element with an animation that hides it after 3 seconds. Use JS to perpetually reset it (remove/add?). When JS disappears, online goes away. Again, bad. But I don’t know how to do anything else with JavaScript not even running.

Comment posted by Ali

+1 and thanks! On the real webpage I already have another hack running in the background. That hack triggers a reflow every second, because Safari on iOS is buggy, and that hack was the only way to fix it. Adding your workaround to this already existing hack won’t make my code uglier. However, there is one tweak I would like to ask. It seems to me that overlay totally covers the page, even if not visible. If that is the case, it is not acceptable: There are interactive elements on the page and the overlay would make those elements unusable.

Comment posted by tmdesigned

Does the pointer-events:none not address this? I am able to still interact.

Comment posted by Ali

My apologizes, I wasn’t aware of this:

Comment posted by Ali

As a side note: I am still surprised that none of the JS is re-run when the page is restored, but the CSS animations are running. It is definitely not what I expected to happen.

Comment posted by tmdesigned

The CSS still working isn’t