We love creating fast and engaging Progressive Web Apps at NearForm. Its part of our DNA to take whatever we do to the next level. Even though we are known for caring about the performance of what we build, we are also known for caring about the end-user experience.
New web standards are coming out often, it’s a constantly changing world with new technologies and frameworks. We like to lead the way on what we do best: rock-solid, blazing-fast and all-around awesome software that incorporates the bleeding edge without compromising compatibility, keeping existing audiences in mind. Progressive Web Apps (PWA) gives us the perfect opportunity to do just that.
In this article we detail how to build progressive web apps covering the following topics:
What are Progressive Web Apps.
What makes Progressive Web Apps “progressive”.
The core principles of Progressive Web Apps: Fast, Reliable and Engaging.
What is an App Shell and how can it help us deliver a great user experience.
What are Progressive Web Apps?
A progressive web app is an enhanced version of a Single Page App (SPA) with a native app feel. Progressive web apps are developed using web standards, but they have some native features because they are fast, smooth and responsive. They are installable on the device, they work regardless of the network state and they engage with a user just like a native-app does.
Besides enabling progressive enhancement, server-side rendering also brings performance and SEO benefits. By making sure the first step is done right and our content is rendered on the server, we have a solid baseline for creating the best experience possible.
Fast – Performance matters
Performance matters the most the first time your user visits. Local caches are empty and resources have to be downloaded from the server through the network. Depending on the network and device being used, there is a period of time where the user has to wait until something is displayed on screen. On high-latency mobile networks like 3G, that period can go up to dozens of seconds if no optimizations are in place.
The amount of time the user has to wait is critical. The user’s attention span decreases as the waiting time increases, ultimately forcing the user to leave after a certain threshold2. This has a huge impact on a business. Studies show that the conversion rates are much higher on web apps that load faster when compared to their slower counterparts, concluding that 1 extra second of page loading can be worth millions in lost revenue!3
In order to avoid this scenario, the load time must be optimized. Looking at a simplified way of how the browser handles the initial page load, some prioritization can be put in place to optimize the critical render path. This helps deliver meaningful content as fast as possible creating the perception that the page is loaded, even if the loading is still ongoing.
A simplified way of how the browser handles the initial page load
Some optimizations that can be used to improve the perceived load time of Progressive Web Apps are:
Improve HTML download time
Reduce HTML size (probably not much to reduce)
Reduce TTFB4 as much as possible: Use CDN / caching for static pages and improve server page rendering performance
Improve other resources download times. For example JS, CSS and so on..
Reduce JS and CSS size by splitting code into separate bundles eg: homepage.js and homepage.css, about_us.js and about_us.css
Serve these resources fast, ideally using a CDN / caching
Use HTTP2 push to send those files reducing overall RTT5
Improve time for render-blocking resources to load
Inline styles above the fold (otherwise page will be unstyled)
Use defer or async on script tags <script src="..." defer="defer">
Improve time for non-render-blocking resources to load
Lazy-load images under the fold and crop them accordingly to their render size
Lazy load other components and parts that are under the fold
Serve these resources fast, ideally using a CDN / caching
Good practice when developing a web app is to set a performance budget. Just like a financial budget, you set how much you are willing to spend on each performance metric. Metrics such as bundle sizes, time to first render, time to interactive, perceptual speed index and so on. There are tools to help you measure these metrics and tell you if you’re on a budget or not, for example, Lighthouse6, WebPagetest7 and Chrome DevTools Despite our focus on load performance, the overall perceived performance of a web app after the load is still relevant. Its long been accepted that users can maintain their sense of flow when the response times are between 0.1 and 1 second. Anything longer than that and the user’s attention diminishes8.
Reliability – Just works every time
The reliability aspect of Progressive Web Apps is fundamental because users must have a fast, instant loading experience every time, regardless of the network conditions. It may happen that the user is offline, the network is really slow or simply in a lie-fi9 situation, where the connection is clearly not what it seems to be. When this happens we turn to service workers. They help us to deliver a reliable experience regardless of connection issues.
Caching Resources and Strategies
Create a Service Worker
Workbox10 is a tool developed by Google that helps the creation of service workers It provides a set of libraries, Node.js modules, command-line tools and plugins for build tools. Workbox can be used as a library for custom-made service workers but can also generate code from a configuration file and can easily be integrated into your build process. An example of a configuration file and the generated service worker is:
swDest: 'public/sw.js', // Generated service worker file destination
globDirectory: 'public/', // Directory to find assets to pre-cache
navigateFallback: '/app-shell', // resource to deliver for any URL (app shell section)
handler: 'cacheFirst', // Strategy to use
maxEntries: 30 // Cache expiration size
maxAgeSeconds: 60 * 60 * 24 // 1 day
Speed isn’t the only reason for caching resources. Service workers are decoupled from the web app itself, they act as a proxy between the web app and the network, therefore, they can provide content from a cache while offline. This gives the opportunity to develop and deliver an offline experience. Ideally, the user won’t even realise the network is gone. The ultimate measure of the reliability of Progressive Web Apps is to never display the infamous dinosaur.
Engaging – Hold your audience’s attention
New browser APIs enable a user-experience that is much closer to a native experience. Standards like the web app manifest or the web push notification help to enable the level of engagement with the user.
If a web app manifest file is provided, the user has the option to install and launch the web app from the home screen. An immersive full-screen experience, together with a loading page can be provided, without location bars or other browser related elements. Push notifications can be sent to the device, driving the user to the web app and increasing the levels of engagement. Permission to send push notifications has to be accepted by the user through a native pop-up interface beforehand.
It’s important to have a sense of opportunity when requesting such permissions. If the user decides to deny this permission once, the push notifications are blocked forever and there is no second chance to request them. Rather than requesting it immediately at the first visit, do it after some level of interaction with the user might increase the chance of acceptance. For example, the user might find it more relevant to receive push notifications after subscribing to a topic on your blog.
The App Shell
One of the core concepts of Progressive Web Apps is speed, they have to be fast. We want the user to get something immediately from the moment a page is revisited. Once the service worker is installed, we can proxy every single network request we want, so why not deliver something straight away instead of waiting for the server to respond? Well, we can and that is one of the basic concepts of an app shell.
An app shell is a container that is capable of loading any parts or pages of our web app. Its the common denominator between all the pages. It usually includes headers, footers, navigation bars, other common graphical components and a router that can display the corresponding page based on the URL that is being visited.
Whereas before we might render every page for a given URL on the server, now with a service worker installed and running, we immediately deliver the app shell for any URL that is visited and let the app shell load the corresponding page.
This makes the app shell an obvious contender to include in the pre-cache strategy. It’s important to keep in mind that the app shell must be as minimal as possible; that is the only way to ensure that loading time is insignificant. Otherwise, a big, chunky app shell will take some time to load, even if it is cached locally, defeating the purpose of instant delivery.
The app shell can be server-side rendered or it can be a static asset. Since we decided to go on a full server-side rendered approach, it makes sense to have the app shell being server-side rendered.
For an in-depth read on what an app shell is we recommend the article by Google in the footer11.
Progressive web apps are awesome and an incredible achievement of the web standards. The first envisionment of mobile apps was based on web technologies, unfortunately, at that time the technology was underdeveloped and that made companies and developers adopt a native platform model. Things have changed; the web has evolved and now we have one single platform that is feature-rich and runs everywhere. One might easily conclude that Progressive Web Apps make native apps almost obsolete! If you decide to give Progressive Web Apps a try and need any help building them get in touch with us at NearForm.
As a companion to this article, we have written two reference Hacker News clones to showcase Progressive Web Apps and everything we discussed above.