Skip to main content

How to build performant web applications for slow networks


How to build performant web applications for slow networks

  4 min read 
How to Build Performant Web Applications for Slow Networks
One of the most important yardsticks for measuring user experience on the web is speed — the time it takes to get meaningful content on the screen and provide an interactive experience. Since a large portion of internet users are running on slow networks, it is imperative to build apps in a way that delivers the best possible experience.
Enter progressive web apps (PWA), which, according to Mozilla, employ modern APIs alongside traditional progressive enhancement strategies to build cross-platform applications. PWAs are more discoverable, work everywhere, and include features that mimic the experience of native apps. Put simply, PWAs are designed to leverage features offered by both modern web browsers and mobile applications. They can even be configured to work offline.
For the purpose of this article, we’ll focus on the offline capability. This feature is facilitated by the service worker API, which enables PWAs to perform reliable and intelligent caching, update background content, execute push notifications, and more. This means that, after a user’s first visit to a website, the site and app will be reliably fast, even on slow networks.
Since most users abandon a slow-loading site after three seconds, it’s especially crucial to make the initial load fast and reliable on slow networks. Let’s outline some methods you can employ to maximize the speed at which your apps deliver content to your users’ screens.

The application shell

Similar to what you’d see in native apps, the app shell is a way to reliably and instantly load your web app on users’ screens. It consists of the minimal HTML, CSS, and JavaScript required to power the user interface.
When cached offline, the app shell can ensure instant, reliable performance on repeated visits because it does not need to be loaded from the network every time; only the necessary content is needed from the network. This approach relies on aggressively caching the shell using a service worker to get the application running. Next, the dynamic content loads for each page using JavaScript. An app shell is useful for getting some initial HTML to the screen fast without a network.
Diagram Showing an Application Shell Caching and Loading Dynamic Content in a Web App
After the initial page is rendered, the service worker downloads the rest of the app’s pages and assets over the network. It also caches the data to deliver a smooth user experience that doesn’t rely heavily on the network.
To use the service worker, it must first be installed. This is done only once at the initial render of the web app. Below is a sample service worker installation script typically included in the index HTML page.
if ('serviceWorker' in navigator) {
            navigator.serviceWorker
                .register("sw.js")
                .then(function () { console.log("Service Worker Registered"); });
        } 
else {
            console.log("Service Worker Registered");
        }
Line 1 simply ensures the browser has service worker support. It then proceeds to install the service worker (sw.js on line 3 is the name of the service worker file). Below is a sample service worker script.
// initializes the cache key
let cacheName = "myServiceWorker-1";

// an array of files to be cached(the app shell)
let filesToCache = [
    'index.html',
    'style.css',
    './assets/poppins.woff2',
    './assets/home.svg'
];

// install event that installs all the files needed by the app shell
self.addEventListener('install', function (e) {
    console.log('[serviceWorker] install')
    e.waitUntil(
      caches.open(cacheName).then(function (cache) {
        console.log('[serviceWorker] caching app shell')
        return cache.addAll(filesToCache)
      })
    )
  })
Since storage space is limited and varies depending on the browser, it is important to store only data that is absolutely relevant.
Chart Showing Storage Space Specifications for Popular Browsers

Handling media assets

Proper handling of media assets is key to delivering an optimal user experience. One way to effectively manage these assets is to use Scalable Vector Graphics (SVG) instead of other image formats such as PNG and JPG. SVGs are great because you can scale them up or down as needed without compromising quality. SVGs are composed of lines, points, and shapes, and browsers render them faster than traditional image formats.
Lazy loading your media assets is another great way to boost initial load time. With lazy loading, you can defer the initialization of objects until they are needed. You can also incrementally deliver the quality of media items based on the user’s current network speed. Products such as Cloudinary offer easy-to-use solutions for media handling and optimization.

Provide a full offline experience

Building performant web apps means ensuring that users enjoy a full and robust experience. A great way to deliver such an experience is to design your app with offline as a core scenario. Designing for offline first can drastically improve your app’s performance by reducing the number of network requests it must make. Instead, resources can be precached and served directly from the local cache. Even with the fastest network connection, serving from the local cache is always guaranteed to be faster.
Again, the service worker is the straw that stirs the drink. The life cycle of the service worker is the most complicated part: if you don’t know what it’s trying to do and what the benefits are, it can feel like it’s fighting you. Once you understand how the service worker operates, you can deliver seamless, unobtrusive updates to users.
Delivering an offline experience typically involves caching, and choosing the right caching strategy depends on the type of resource you’re trying to cache and how you might need to access it later. Precaching your resources is similar to what happens when a user installs a desktop or mobile app. The key resources the app needs to run are installed or cached on the device so they can be loaded later, whether there’s a network connection or not. Pulling from the local cache eliminates any network variability. No matter what kind of network the user is on — Wi-Fi, 5G, 3G, or even 2G — the resources we need for the app to run are available almost immediately.
The following code illustration uses the cache-first approach.
// fetch event handler that tries to get requested file from the cache first then the network if it doesn't find it in the cache
  self.addEventListener('fetch', function (e) {
    console.log('[serviceWorker] fetch', e.request.url)
    e.respondWith(
      caches.match(e.request).then(function (response) {
        return response || fetch(e.request)
      })
    )
  })

// generic fallback
  self.addEventListener('fetch', function (event) {
    event.respondWith(
      // Try the cache
      caches.match(event.request).then(function (response) {
        // Fall back to network
        return response || fetch(event.request)
      }).catch(function () {
        // If both fail, show a generic fallback:
        // console.log('offline oo');
        alert("You are offline. Check Your Internt Connection and try again")
       // return caches.match('/offline.html')
      // However, in reality you'd have many different
      // fallbacks, depending on URL & headers.
      // Eg, a fallback silhouette image for avatars.
      })
    )
  })
This approach is especially useful for content-intensive web apps such as news sites, ensuring that content is always available to users and that they are notified when new content is available.

Conclusion

Failure to build performant web apps could lead to disenfranchised users, given that a large portion of global internet users are running on slow networks. On the other hand, performant web apps can significantly boost user engagement, which is why you should design apps and websites with these principles in mind going forward.

Comments

Popular posts from this blog

4 Ways to Communicate Across Browser Tabs in Realtime

1. Local Storage Events You might have already used LocalStorage, which is accessible across Tabs within the same application origin. But do you know that it also supports events? You can use this feature to communicate across Browser Tabs, where other Tabs will receive the event once the storage is updated. For example, let’s say in one Tab, we execute the following JavaScript code. window.localStorage.setItem("loggedIn", "true"); The other Tabs which listen to the event will receive it, as shown below. window.addEventListener('storage', (event) => { if (event.storageArea != localStorage) return; if (event.key === 'loggedIn') { // Do something with event.newValue } }); 2. Broadcast Channel API The Broadcast Channel API allows communication between Tabs, Windows, Frames, Iframes, and  Web Workers . One Tab can create and post to a channel as follows. const channel = new BroadcastChannel('app-data'); channel.postMessage(data); And oth...

Certbot SSL configuration in ubuntu

  Introduction Let’s Encrypt is a Certificate Authority (CA) that provides an easy way to obtain and install free  TLS/SSL certificates , thereby enabling encrypted HTTPS on web servers. It simplifies the process by providing a software client, Certbot, that attempts to automate most (if not all) of the required steps. Currently, the entire process of obtaining and installing a certificate is fully automated on both Apache and Nginx. In this tutorial, you will use Certbot to obtain a free SSL certificate for Apache on Ubuntu 18.04 and set up your certificate to renew automatically. This tutorial will use a separate Apache virtual host file instead of the default configuration file.  We recommend  creating new Apache virtual host files for each domain because it helps to avoid common mistakes and maintains the default files as a fallback configuration. Prerequisites To follow this tutorial, you will need: One Ubuntu 18.04 server set up by following this  initial ...

Working with Node.js streams

  Introduction Streams are one of the major features that most Node.js applications rely on, especially when handling HTTP requests, reading/writing files, and making socket communications. Streams are very predictable since we can always expect data, error, and end events when using streams. This article will teach Node developers how to use streams to efficiently handle large amounts of data. This is a typical real-world challenge faced by Node developers when they have to deal with a large data source, and it may not be feasible to process this data all at once. This article will cover the following topics: Types of streams When to adopt Node.js streams Batching Composing streams in Node.js Transforming data with transform streams Piping streams Error handling Node.js streams Types of streams The following are four main types of streams in Node.js: Readable streams: The readable stream is responsible for reading data from a source file Writable streams: The writable stream is re...