Service Worker (sw.js) should always return offline.html document if there is no network connection

I’m having an issue with a service worker that’s working partially. The manifest defines the start_url correctly (https://example.com/start.html) for users that add the website to the Homescreen, and both the start.html and offline.html are cached correctly as well, and both are available while the browser has no internet connection.

If the user gets offline (no network connection), the service worker successfully serves both https://example.com/start.html and https://example.com/offline.html — but if the user tries opening anything else (e.g. https://example.com/something.html) the browser throws a "site can’t be reached" error message.

What I actually need, is that, if there is no network connection, the service worker always returns the offline.html cached document, no matter which url the user is trying to reach.

In other words, the problem is that the Service Worker is not properly serving offline.html for the user’s requests when there’s no network connection (whatever solution is found, it also needs to cache the start.html for the manifest’s start_url).

This is my current code:

manifest.json

{
    "name": "My Basic Example",
    "short_name": "Example",
    "icons": [
        {
            "src": "https://example.com/static/ico/manifest-192x192.png",
            "sizes": "192x192",
            "type": "image/png"
        },
        {
            "src": "https://example.com/static/ico/manifest-512x512.png",
            "sizes": "512x512",
            "type": "image/png",
            "purpose": "any maskable"
        }
    ],
    "start_url": "https://example.com/start.html",
    "scope": "/",
    "display": "standalone",
    "orientation": "portrait",
    "background_color": "#2196f3",
    "theme_color": "#2196f3"
}

core.js

if('serviceWorker' in navigator) {
    navigator.serviceWorker.register('sw.js', {
        scope: '/'
    }).then(function(registration) {
    }).catch(function(err) {
    });
    navigator.serviceWorker.ready.then(function(registration) {
    });
}

sw.js

const PRECACHE = 'cache-v1';
const RUNTIME = 'runtime';
const PRECACHE_URLS = [
    '/offline.html',
    '/start.html'
];
self.addEventListener('install', event => {
    event.waitUntil(
        caches.open(PRECACHE)
        .then(cache => cache.addAll(PRECACHE_URLS))
        .then(self.skipWaiting())
    );
});
self.addEventListener('activate', event => {
    const currentCaches = [PRECACHE, RUNTIME];
    event.waitUntil(
        caches.keys().then(cacheNames => {
            return cacheNames.filter(cacheName => !currentCaches.includes(cacheName));
        })
        .then(cachesToDelete => {
            return Promise.all(cachesToDelete.map(cacheToDelete => {
                return caches.delete(cacheToDelete);
            }));
        })
        .then(() => self.clients.claim())
    );
});
self.addEventListener('fetch', event => {
    if(event.request.url.startsWith(self.location.origin)) {
        event.respondWith(
            caches.match(event.request).then(cachedResponse => {
                if(cachedResponse) {
                    return cachedResponse;
                }
                return caches.open(RUNTIME).then(cache => {
                    return fetch(event.request).then(response => {
                        return cache.put(event.request, response.clone()).then(() => {
                            return response;
                        });
                    });
                });
            })
        );
    }
});

Any ideas? Thanks!

Leave a Comment