Handling broken images with the service worker

From bitsofcode: https://bitsofco.de/handling-broken-images-with-service-worker/

Handling broken images with the service workerA few years ago, I wrote about how we can use css to style broken images. The technique leveraged on the fact that any styling to the ::before or ::after pseudo-elements on the <img> element will only be applied if the image doesn’t load. So, we could style those elements and they would only display if the image was broken.

Here’s an example of how I’ve styled broken images on this site:

There are pros and cons to handling broken images this way. One limitation is browser support, as this technique doesn’t work in some major browsers like Safari.

Having recently done a lot of work with service workers, it occurred to me that we could use the service worker to handle broken images in a different way. Since the service worker can tell if an image file isn’t able to be fetched, we can handle that condition by, for example, serving a different image to the browser.

Intercepting requests for broken images

In the service worker fetch event, we can tell if and when a request the browser makes goes wrong, whether that be because the user is offline or because the response to the fetch request was bad.

self.addEventListener('fetch', (e) => {
    e.respondWith(
        fetch(e.request)
            .then((response) => {
                if (response.ok) return response;
                
                // User is online, but response was not ok

            })
            .catch((err) => {

                // User is probably offline

            })
    )
});

In either of these scenarios, we can check to see if the failed request was for an image and do whatever we like in response.

function isImage(fetchRequest) {
    return fetchRequest.method === "GET" 
           && fetchRequest.destination === "image";
}

self.addEventListener('fetch', (e) => {
    e.respondWith(
        fetch(e.request)
            .then((response) => {
                if (response.ok) return response;
                
                // User is online, but response was not ok
                if (isImage(e.request)) {
                    // do something
                }

            })
            .catch((err) => {

                // User is probably offline
                if (isImage(e.request)) {
                    // do something
                }

            })
    )
});

Serving a “broken image” image

One way to handle requests for images that don’t resolve would be to send a placeholder image in its place. For example, we can have an image like the one below to show the user that the image is broken.

Handling broken images with the service worker

We can implement this by responding to the fetch request with a new request for the palceholder file, /broken.png.

self.addEventListener('fetch', (e) => {
    e.respondWith(
        fetch(e.request)
            .then((response) => {
                if (response.ok) return response;
                
                // User is online, but response was not ok
                if (isImage(e.request)) {
                    // Fetch the broken image placeholder instead
                    return fetch("/broken.png");
                }

            })
            .catch((err) => {

                // User is probably offline
                if (isImage(e.request)) {
                    // do something
                }

            }) // end fetch
    )
});

This will work when the user is online, but if we want this to also work offline, we will need to cache the placeholder image. This is typically done during the install phase of the service worker lifecycle.

self.addEventListener('install', (e) => {
    self.skipWaiting();
    e.waitUntil(
        caches.open("precache").then((cache) => {
        
            // Add /broken.png to "precache"
            cache.add("/broken.png");
            
        })
    );
});

Once the image is in the cache, we can respond to the fetch request with a response from the cache instead of having to make a new request over the network.

self.addEventListener('fetch', (e) => {
    e.respondWith(
        fetch(e.request)
            .then((response) => {
                if (response.ok) return response;
                
                // User is online, but response was not ok
                if (isImage(e.request)) {
                    // Get broken image placeholder from cache
                    return caches.match("/broken.png");
                }

            })
            .catch((err) => {

                // User is probably offline
                if (isImage(e.request)) {
                    // Get broken image placeholder from cache
                    return caches.match("/broken.png");
                }

            })
    )
});

I created a GitHub Gist with the full implementation in case you want to use it in your own projects.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.