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.
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.
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.
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.
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.
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.
While implementing a PWA can have substantial performance benefits, monitoring overall performance of your app is key. If you’re interested in understanding performance issues in your production app, try LogRocket. https://logrocket.com/signup/
LogRocket is like a DVR for web apps, recording literally everything that happens on your site. Instead of guessing why problems happen, you can aggregate and report on performance issues to quickly understand the root cause.
LogRocket instruments your app to record requests/responses with headers + bodies along with contextual information about the user to get a full picture of an issue. It also records the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.
Make performance a priority – Start monitoring for free.
Hey there, want to help make our blog better?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowSOLID principles help us keep code flexible. In this article, we’ll examine all of those principles and their implementation using JavaScript.
JavaScript’s Date API has many limitations. Explore alternative libraries like Moment.js, date-fns, and the new Temporal API.
Explore use cases for using npm vs. npx such as long-term dependency management or temporary tasks and running packages on the fly.
Validating and auditing AI-generated code reduces code errors and ensures that code is compliant.