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

How to use Ngx-Charts in Angular ?

Charts helps us to visualize large amount of data in an easy to understand and interactive way. This helps businesses to grow more by taking important decisions from the data. For example, e-commerce can have charts or reports for product sales, with various categories like product type, year, etc. In angular, we have various charting libraries to create charts.  Ngx-charts  is one of them. Check out the list of  best angular chart libraries .  In this article, we will see data visualization with ngx-charts and how to use ngx-charts in angular application ? We will see, How to install ngx-charts in angular ? Create a vertical bar chart Create a pie chart, advanced pie chart and pie chart grid Introduction ngx-charts  is an open-source and declarative charting framework for angular2+. It is maintained by  Swimlane . It is using Angular to render and animate the SVG elements with all of its binding and speed goodness and uses d3 for the excellent math functio...

Understand Angular’s forRoot and forChild

  forRoot   /   forChild   is a pattern for singleton services that most of us know from routing. Routing is actually the main use case for it and as it is not commonly used outside of it, I wouldn’t be surprised if most Angular developers haven’t given it a second thought. However, as the official Angular documentation puts it: “Understanding how  forRoot()  works to make sure a service is a singleton will inform your development at a deeper level.” So let’s go. Providers & Injectors Angular comes with a dependency injection (DI) mechanism. When a component depends on a service, you don’t manually create an instance of the service. You  inject  the service and the dependency injection system takes care of providing an instance. import { Component, OnInit } from '@angular/core'; import { TestService } from 'src/app/services/test.service'; @Component({ selector: 'app-test', templateUrl: './test.component.html', styleUrls: ['./test.compon...

How to solve Puppeteer TimeoutError: Navigation timeout of 30000 ms exceeded

During the automation of multiple tasks on my job and personal projects, i decided to move on  Puppeteer  instead of the old school PhantomJS. One of the most usual problems with pages that contain a lot of content, because of the ads, images etc. is the load time, an exception is thrown (specifically the TimeoutError) after a page takes more than 30000ms (30 seconds) to load totally. To solve this problem, you will have 2 options, either to increase this timeout in the configuration or remove it at all. Personally, i prefer to remove the limit as i know that the pages that i work with will end up loading someday. In this article, i'll explain you briefly 2 ways to bypass this limitation. A. Globally on the tab The option that i prefer, as i browse multiple pages in the same tab, is to remove the timeout limit on the tab that i use to browse. For example, to remove the limit you should add: await page . setDefaultNavigationTimeout ( 0 ) ;  COPY SNIPPET The setDefaultNav...