skip to content

Network Aware Image Optimization

Users visit websites from very different network conditions. Some countries are already looking at rolling out 5G whereas parts of other countries are able to come online only because of giant companies beaming down internet from drones, satellites and even hot air balloons! Even users within a single country typically experience varying speeds depending on network congestion levels, the time of day, where they are, etc. With latencies and bandwidth varying a lot between these different connection types, the experience of users visiting your website would vary a lot too.

In this post, we will see how to deliver a good user experience for users on slower networks without compromising the experience of users who are on the fast networks.

Videos already do a good job of adapting to your network speeds if encoded properly. Let’s look at how to optimise images in a more intelligent way using Service workers (which have been talked about a lot already) and the NetInfo API.

The NetInfo API

The network information API provides information about the network the user is on like the bandwidth (downlink), round trip time (rtt), connection type (effectiveType) in JavaScript land. Unfortunately this API is only supported in Chrome-like browsers. For other browsers, we will gracefully degrade to sending the original assets.

Putting it all together

In this service worker script, I am varying different optimisation modes depending on the type of network the user is on. On the fetch handler, we intercept image requests by checking the request.destination. We then modify these URLs to request the right asset. In this way, users on fast internet connections are delivered images which are only optimised by lossless algorithms and users on slower networks are more aggressively optimised.

function modifyURL(url) {
  // ect can be 'slow-2g', '2g', '3g', or '4g'.
  const connectionType = navigator.connection.effectiveType;
  if (connectionType === "slow-2g" || connectionType === "2g") {
    return url + "?opt=aggressive";
  } else if (connectionType === "4g") {
    return url + "?opt=mild";
  } else {
    return url + "?opt=default";
  }
}

self.addEventListener("fetch", async event => {
  if (event.request.method != "GET") {
    return;
  }

  if (event.request.destination != "image") {
    return;
  }

  event.respondWith(fetch(modifyURL(event.request.url)));
});

In this script, I am just using the connection type to decide which variation of the asset is to be delivered to the user. But you can also use other (combinations of) attributes in the NetInfo API to better segment your users.

This script can further be enhanced by accounting for changes in network conditions. Right now, when the user goes from a 4G environment to a 2G environment, the script downloads a new asset even if the higher quality image is already in the browser cache. We can store the downloaded images in the Service Worker Cache instead and check the cache if a version of the asset corresponding to a faster network is already there.

What else can you do?

The Save data header (which is also unfortunately Chrome-only) is a way for the user to indicate that he is on a metered connection and wants to download lesser bytes if possible. This is exposed as a header, which means that you can detect and serve different experiences to these users from your server (no service workers required!). This is also exposed in JavaScript via navigator.connection.saveData. Since this is just a Boolean value, it is not as fine-grained as the NetInfo API but you can use both of them together for a better understanding of the user’s network conditions.

You can also consider sending Low Quality Image Placeholders (LQIP) for users on very slow connections or for users with the save-data setting enabled.

Connection aware components are another interesting way to go further in this direction.

What are the other ways you can think of to be kind to users with slower connections? Let us know in the comments below.