'How do I load only this page when my progressive web app goes offline

I'm stuck on a problem: I wanted to make some tests trying to turn my simple flask web application into a progressive web application even tho I found poor information about it. This is the structure of my project:

│   __init__.py
│
├───blueprints
│       index.py
│       pwa.py
│       __init__.py
│
├───static
│   │   app.js
│   │   manifest.json
│   │   offline.html
│   │   sw.js
│   │
│   └───images
│       └───icons
│
└───templates
        index.html
        layout.html
        other.html

Where in the service worker I made this file based on Heroku's example:

app.js

if (!navigator.serviceWorker.controller) {
    navigator.serviceWorker.register("/sw.js").then(function (reg) {
        console.log("Service worker has been registered for scope: " + reg.scope);
    });
}

sw.js

self.addEventListener("install", function (event) {
    event.waitUntil(preLoad());
});

const preLoad = async function () {
    console.log("Installing web app");
    const cache = await caches.open("offline");
    console.log("caching index and important routes");
    return await cache.addAll(["/", "static/offline.html"]);
};

self.addEventListener("fetch", function (event) {
    event.respondWith(checkResponse(event.request).catch(function () {
        return returnFromCache(event.request);
    }));
    event.waitUntil(addToCache(event.request));
});

const checkResponse = function (request) {
    return new Promise(function (fulfill, reject) {
        fetch(request).then(function (response) {
            if (response.status !== 404) {
                fulfill(response);
            } else {
                reject();
            }
        }, reject);
    });
};

const addToCache = async function (request) {
    const cache = await caches.open("offline");
    const response = await fetch(request);
    console.log(response.url + " was cached");
    return await cache.put(request, response);
};

const returnFromCache = async function (request) {
    const cache = await caches.open("offline");
    const matching = await cache.match(request);
    if (!matching || matching.status === 404) {
        return cache.match("offline.html");
    } else {
        return matching;
    }
};

Yet I don't understand why if I go offline and try to reload either the web app or the PWA, only the index is loaded whereas I just want to load the offline template page, thus I think it to be cached, to load



Solution 1:[1]

There's an example of creating a dedicated offline fallback page in this post.

If all you care about is showing an offline fallback page, the main important point is to ensure that you only trigger your fallback logic and call event.respondWith() if the fetch handler is dealing with a navigation request. Here's a relevant snippet of code from that post:

// Incrementing OFFLINE_VERSION will kick off the install event and force
// previously cached resources to be updated from the network.
// This variable is intentionally declared and unused.
const OFFLINE_VERSION = 1;
const CACHE_NAME = "offline";
// Customize this with a different URL if needed.
const OFFLINE_URL = "offline.html";

self.addEventListener("install", (event) => {
  event.waitUntil(
    (async () => {
      const cache = await caches.open(CACHE_NAME);
      // Setting {cache: 'reload'} in the new request will ensure that the
      // response isn't fulfilled from the HTTP cache; i.e., it will be from
      // the network.
      await cache.add(new Request(OFFLINE_URL, { cache: "reload" }));
    })()
  );
  // Force the waiting service worker to become the active service worker.
  self.skipWaiting();
});

self.addEventListener("activate", (event) => {
  event.waitUntil(
    (async () => {
      // Enable navigation preload if it's supported.
      // See https://developers.google.com/web/updates/2017/02/navigation-preload
      if ("navigationPreload" in self.registration) {
        await self.registration.navigationPreload.enable();
      }
    })()
  );

  // Tell the active service worker to take control of the page immediately.
  self.clients.claim();
});

self.addEventListener("fetch", (event) => {
  // We only want to call event.respondWith() if this is a navigation request
  // for an HTML page.
  if (event.request.mode === "navigate") {
    event.respondWith(
      (async () => {
        try {
          // First, try to use the navigation preload response if it's supported.
          const preloadResponse = await event.preloadResponse;
          if (preloadResponse) {
            return preloadResponse;
          }

          // Always try the network first.
          const networkResponse = await fetch(event.request);
          return networkResponse;
        } catch (error) {
          // catch is only triggered if an exception is thrown, which is likely
          // due to a network error.
          // If fetch() returns a valid HTTP response with a response code in
          // the 4xx or 5xx range, the catch() will NOT be called.
          console.log("Fetch failed; returning offline page instead.", error);

          const cache = await caches.open(CACHE_NAME);
          const cachedResponse = await cache.match(OFFLINE_URL);
          return cachedResponse;
        }
      })()
    );
  }

  // If our if() condition is false, then this fetch handler won't intercept the
  // request. If there are any other fetch handlers registered, they will get a
  // chance to call event.respondWith(). If no fetch handlers call
  // event.respondWith(), the request will be handled by the browser as if there
  // were no service worker involvement.
});

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Jeff Posnick