Custom 404 Page Without a Server

I recently wanted to know if it was possible to create a custom 404 page for a website if you didn't have access to the backend server. I thought it might be difficult since without a server you wouldn't be able to catch the request for pages and return the 404 page based on a failed request.

After my research didn't turn up anything, I had an idea that I thought might work.

I remembered that Service Workers could intercept network requests and respond with cached resources. Typically this is used to serve a custom offline page when the user navigates to a page that hasn't been cached yet. But what if we could use that same principle to serve a custom 404 page when the request fails?

There wasn't a lot of information available on doing something that like, but I did find a Google lab article on Service Workers which had an small section on serving a custom 404 from the cache. They didn't list the solution in the article, so I had to open their github repo to find it.

Turns out that it is indeed possible to serve a custom 404 page from a service worker. All it takes is first caching the 404 page and then looking at the response.status of a fetch request. If it's 404, return the cached 404 page.

Now whenever the user enters a URL that isn't found, the Service Worker will intercept the request and return your custom 404. No server required.

const filesToCache = [
  '/',
  '404.html'
];
const staticCacheName = 'pages-cache-v1';

self.addEventListener('install', event => {
  event.waitUntil(
    caches.open(staticCacheName).then(cache => {
      return cache.addAll(filesToCache);
    });
  );
});

self.addEventListener('fetch', event => {
  event.respondWith(
    caches.match(event.request).then(response => {
      // found a cached resource
      if (response) {
        return response;
      }

      // request the non-cached resource
      return fetch(event.request).then(response => {

        // fetch request returned 404, serve custom 404 page
        if (response.status === 404) {
          return caches.match('404.html');
        }
      });
    });
  );
});