Skip to main content

Modify the assets offered to users based on the conditions of their device and network.


Updated

It appears in:
Network reliability

Users access websites through a wide variety of network connections and devices. Even in major cities, where mobile networks are fast and reliable, one may end up experiencing slower loading times, for example when traveling on the subway, in a car, or just moving around. In regions like emerging markets, this phenomenon is even more common, not only due to unreliable networks, but also because devices tend to have less memory and CPU processing power.

Adaptive loading is a web performance pattern that allows you to adapt your site based on the user's network and device conditions.

The adaptive load pattern is made possible by service workers, the Network Information API, the Hardware Concurrency API, and the Device memory API. In this guide, we explore how you can use service workers and the Network Information API to achieve an adaptive loading strategy.

Production case

Terra is one of the largest media companies in Brazil. It has a large user base, coming from a wide variety of devices and networks.

To provide a more reliable experience for all of its users, Terra combines service workers and Network Information API to offer lower quality images to users on 2G or 3G connections.

terra-adaptive-images-3516746

The company also found that scripts and assets (such as banners) uploaded by ad networks were especially harmful to users browsing on 3G or slower connections.

As is the case with many publishers, Terra serves AMP versions of its pages to users from search engines and other link exchange platforms. AMP pages are typically lightweight and help mitigate the impact of ads on performance by reducing the priority of their loading over the main content on the page.

With this in mind, Terra decided to start offering AMP versions of its pages not only to users coming from search engines, but also to those browsing its site on 3G or slower connections.

To achieve this, they use the Network Information API on the service worker to detect if the request is coming from 3G or slower. If that's the case, they change the page's URL to request the AMP version of the page.

terra-adaptive-amp-9466834

Thanks to this technique, they send 70% fewer bytes to users with slower connections. the time used on AMP pages is higher for 3G users and ads on AMP pages have a better CTR (clickthrough rate) for that group.

Implement adaptive loading with Workbox

In this section, we will explore how Workbox can be used to implement adaptive loading strategies.

Workbox provides several out-of-the-box runtime caching strategies. They are used to indicate how the service worker generates a response after receiving a fetch event.

For example, in a Cache first strategy the Request it will be fulfilled using the cached response (if available). If there is no cached answer, Request it will be fulfilled by a network request and the response will be cached.

import { registerRoute } from 'workbox-routing' ;
import { CacheFirst } from 'workbox-strategies' ;

registerRoute (
new RegExp ( '/ img /' ) ,
new CacheFirst ( )
) ;

Caching strategies can be customized with Workbox Plugins. These allow you to add additional behaviors by manipulating requests and responses during the life cycle of a request. Workbox has several built-in plugins for common cases and APIs, but you can also define a custom pluginand enter some custom logic of your choice.

To achieve load adaptation, define a custom plugin, called, for example, adaptiveLoadingPlugin:

const adaptiveLoadingPlugin = {
requestWillFetch : async ( { request } ) => {
const urlParts = request . url . split ( '/' ) ;
let imageQuality ;

switch (
navigator && navigator . connection
? navigator . connection . effectiveType
: ''
) {
case '3g' :
imageQuality = 'q_30' ;
break ;
}

const newUrl = urlParts
. splice ( urlParts . length - 1 , 0 , imageQuality )
. join ( '/' )
. replace ( '.jpg' , '.png' ) ;
const newRequest = new Request ( newUrl . href , { headers : request . headers } ) ;

return newRequest ;
} ,
} ;

The above code does the following:

  • Implement a requestWillFetch () callback: called every time a network request is to be made, so you can modify the Request.
  • Check the type of connection, using the Network Information API. Based on the state of the network, it creates a new URL part, which indicates the quality of the image to be searched (eg. q_30 for 3G users).
  • Create a new URL based on dynamics newPart value and returns the new Request to perform, based on that URL.

Then pass the complement to a cacheFirst strategy that contains a regular expression to match the image urls (ex. / img /):

workbox . routing . registerRoute (
new RegExp ( '/ img /' ) ,
workbox . strategies . cacheFirst ( {
cacheName : 'images' ,
plugins : [
adaptiveLoadingPlugin ,
workbox . expiration . Plugin ( {
maxEntries : 50 ,
purgeOnQuotaError : true ,
} ) ,
] ,
} ) ,
) ;

As a result, when image requests are intercepted, the runtime caching strategy will try to satisfy the request from the cache. If not available, it will run logic in the plugin to decide what image quality to retrieve from the network.

Finally, the response will be cached and sent back to the page.

Cloudinary Workbox Plugin

Cloudinary, an image and video hosting service, has Workbox Plugin which encapsulates the functionality explained in the previous section, making it even easier to implement.

cloudinary-workbox-5686987

The plugin is designed to work with Workbox Web Pack Plugin. To implement it, use the GenerateSW () class:

new workboxPlugin . GenerateSW ( {
swDest : 'sw.js' ,
importScripts : [ './cloudinaryPlugin.js' ] ,
runtimeCaching : [
{
urlPattern : new RegExp ( '^ https: //res.cloudinary.com/.*/image/upload/' ) ,
handler : 'CacheFirst' ,
options : {
cacheName : 'cloudinary-images' ,
plugins : [
{
requestWillFetch : async ( { request } ) =>
cloudinaryPlugin . requestWillFetch ( request ) ,
} ,
] ,
} ,
} ,
] ,
} ) ;

The above code does the following:

  • Use the GenerateSW () class to configure webpack to generate a service worker on the target indicated in swDest.
  • Import the cloudinary plugin script.
  • Defines a Cache First runtime caching strategy for image requests to Cloudinary CDN.
  • Pass the Cloudinary Workbox Plugin to adjust the image quality according to network conditions.

Explore more adaptable charging strategies

You can go further by mapping device signals, such as hardware concurrency and Memory device to device categories and then offer different assets based on device type (low-end, mid-range, or high-end).

error: Attention: Protected content.