Fallback Offline Page in Django

Table of Contents

Django PWA Tutorial Series:

  1. Introduction
  2. Add Web app manifest to Django
  3. Add Service Worker to Django
  4. Fallback Offline Page in Django
  5. Caching and Routing (coming soon)
  6. How to add install button to PWA (coming soon)
  7. Send Web Push Notification from Django (part 1) (coming soon)
  8. Send Web Push Notification from Django (part 2) (coming soon)

The source code is available on https://github.com/AccordBox/django-pwa-demo

Objectives

By the end of this chapter, you should be able to:

  1. Use Service worker to cache the offline page, and return cached response when network is not available.

Offline Page

First, we create an offline page in Django

Update django_pwa_app/urls.py

from django.contrib import admin
from django.urls import path
from django.views.generic import TemplateView
import django_pwa_app.views

urlpatterns = [
    path('', TemplateView.as_view(template_name="index.html")),
    path('sw.js', django_pwa_app.views.service_worker),
    path('offline/', TemplateView.as_view(template_name="offline.html")),        # new
    path('admin/', admin.site.urls),
]

Create django_pwa_app/templates/offline.html

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8" />
  <meta http-equiv="X-UA-Compatible" content="IE=edge" />
  <meta name="viewport" content="width=device-width, initial-scale=1" />

  <title>You are offline</title>

  <!-- Inline the page's stylesheet. -->
  <style>
    body {
      font-family: helvetica, arial, sans-serif;
      margin: 2em;
    }

    h1 {
      font-style: italic;
      color: #373fff;
    }

    p {
      margin-block: 1rem;
    }

    button {
      display: block;
    }
  </style>
</head>
<body>
<h1>You are offline</h1>

<p>
  The page will automatically reload once the connection is re-established.
  Click the button below to try reloading manually.
</p>
<button type="button">⤾ Reload</button>

<!-- Inline the page's JavaScript file. -->
<script>
  document.querySelector('button').addEventListener('click', () => {
    window.location.reload();
  });

  // Listen to changes in the network state, reload when online.
  // This handles the case when the device is completely offline.
  window.addEventListener('online', () => {
    window.location.reload();
  });

  // Check if the server is responding & reload the page if it is.
  // This handles the case when the device is online, but the server
  // is offline or misbehaving.
  async function checkNetworkAndReload() {
    try {
      const response = await fetch('.');
      // Verify we get a valid response from the server
      if (response.status >= 200 && response.status < 500) {
        window.location.reload();
        return;
      }
    } catch {
      // Unable to connect to the server, ignore.
    }
    window.setTimeout(checkNetworkAndReload, 2500);
  }

  checkNetworkAndReload();

</script>
</body>
</html>

Notes:

  1. The above source code come from Create an offline fallback page
  2. It demonstrates both manual reload based on a button press as well as automatic reload based on the online event and regular server polling.
(env)$ python manage.py runserver
# check on http://127.0.0.1:8000/offline/

We wil see the offline page, and it will keep reloading. This is the correct behavior.

Let's close the page.

Service Worker

Update frontend/src/sw.js

const manifest = self.__WB_MANIFEST;
if (manifest) {
  // do nothing
}

// https://web.dev/offline-fallback-page/
const CACHE_NAME = 'offline-html';
const FALLBACK_HTML_URL = '/offline/';
self.addEventListener('install',  (event) => {
  event.waitUntil(
    // 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.
    caches.open(CACHE_NAME)
      .then((cache) => cache.add(
        new Request(FALLBACK_HTML_URL, { cache: "reload" })
      ))
  );

  // Force the waiting service worker to become the active service worker.
  self.skipWaiting();
});

self.addEventListener('activate', function(event) {
  // Tell the active service worker to take control of the page immediately.
  self.clients.claim();
});

self.addEventListener("fetch", event => {
  event.respondWith(
    caches.match(event.request)
      .then(response => {
        return response || fetch(event.request);
      })
      .catch(() => {
        return caches.match(FALLBACK_HTML_URL);
      })
  );
});

Notes:

  1. In the install event handler, we add the response of FALLBACK_HTML_URL to the CacheStorage
  2. In the fetch event handler, we use caches.match to checks if a match for the request is found in the CacheStorage. If so, use it, if not, fetch the request.
  3. If the fetch(event.request) fail, try to serve the fallback offline page

Simple Test

$ cd frontend
$ npm run watch
# in another terminal
(env)$ python manage.py runserver

If we visit http://127.0.0.1:8000/, and check the Cache Storage, we will see the page response has been stored in the Cache Storage

Now, let's terminate the Django server, and try to reload the page again.

As you can see, the offline page is served instead. And it will do regular server polling.

If we run Django dev server again, then the home page will display again!

Django PWA Tutorial Series:

  1. Introduction
  2. Add Web app manifest to Django
  3. Add Service Worker to Django
  4. Fallback Offline Page in Django
  5. Caching and Routing (coming soon)
  6. How to add install button to PWA (coming soon)
  7. Send Web Push Notification from Django (part 1) (coming soon)
  8. Send Web Push Notification from Django (part 2) (coming soon)

The source code is available on https://github.com/AccordBox/django-pwa-demo

Launch Products Faster with Django

SaaS Hammer helps you launch products in faster way. It contains all the foundations you need so you can focus on your product.

Michael Yin

Michael Yin

Michael is a Full Stack Developer from China who loves writing code, tutorials about Django, and modern frontend tech.

He has published some ebooks on leanpub and tech course on testdriven.io.

© 2024 SaaS Hammer