In this tutorial, we will be diving into introductions to building Progressive Web Apps.
Progressive Web Apps are one of the technologies originally proposed by Google to make the web a better place for everyone. A better place in the sense that the web is fast and easily accessible to users no matter where they are and even with the poorest of connections.
I’m sure by now you must have heard or read about Progressive Web Applications. Progressive Web Applications are experiences that combine the best of web applications and the best of mobile applications. They use service workers, HTTPS, a manifest file and an app shell architecture to deliver native app experiences to web applications.
A service worker is a script that your browser runs in the background, separate from the web page, opening the door to features that don’t need a web page or user interaction. They include features such as push notifications and background sync.
I’ll highlight more about these features later in this article.
The term Progressive Web Apps was first coined by Frances Berriman and Alex Russell in 2015, as a way of describing applications that take advantage of new features supported by modern browsers, including service workers and web app manifests, and also let users upgrade web apps to progressive web applications regardless of their native operating system.
Progressive Web Apps are also described as user experiences that have the reach of the web, and are largely characterized by the following:
– Reliable – Load instantly and never show the “No Internet Connection” page, even in uncertain network conditions.
– Fast – Respond quickly to user interactions with silky smooth animations and no janky scrolling.
– Engaging – Feel like a natural app on the device, with an immersive user experience.
So the question is, as a developer, product manager or CTO, why do I need a Progressive Web Application? Do I really need a Progressive Web Application? Is it all just hype and noise?
No it isn’t. The advantages of Progressive Web Apps (PWA) are massive and I’ll highlight them below:
Progressive Web Apps are also very much in use by some of the biggest companies and are gaining popularity.
Twitter
Twitter Lite (mobile.twitter.com) was built as a Progressive Web App and that has helped with the overall user experience of the site when used from a mobile device. It became the default mobile web experience for all users globally in April 2017. Twitter developed Twitter Lite to deliver a more robust experience, with explicit goals for instant loading, user engagement, and lower data consumption.
As most of the world is using 2G or 3G networks, it’s important that users can access the site in a fast manner, therefore Twitter Lite is interactive in under 5 seconds (on initial load) over 3G on most devices. It offers offline support so you will not be interrupted while using Twitter if you temporarily lose your connection.
When compared to the native mobile app, the Twitter Lite PWA is only 600KB whereas the native Android app requires about 25MB for download and the native iOS app is about 215MB in size.
Housing
Housing.com is an Indian based real estate search portal which allows customers to search for housing based on geography, number of rooms, and various other filters. It is one of the leading online real estate platforms in India. In 2016, the Housing engineering team set out to build the mobile web of the site as a Progressive Web App that the results were worth it.
They saw a 38% increase in total conversions across browsers and the bounce rate fell by over 40%. The PWA also led to an increase of page-load performance by over 30% and the average time-per-session also increased by 10% across all browsers.
Due to the fact that most Indian users reach the Internet via 2G and 3G networks, a fast user experience is essential. To decrease load times, Housing.com utilized the Service Worker technology and streamlined their site to help consumers quickly find what they’re looking for. Users can continue browsing properties and reviewing previous searches, all while offline.
Uber
Uber realized that to reach new customers in various parts of the world, they need a site that loads fast – even on 2G. The solution to that was a PWA.
Uber also utilizes PWA for their mobile web application which can be seen at m.uber.com. This offers an app-like experience for riders using low-end devices, including devices that might not be necessarily supported by the native application. The app is also tiny in size—the core ride request app comes in at just 50kB, enabling the app to load quickly even on 2G networks.
The mobile web app uses Service Workers and Local Storage to provide a meaningful experience for users even on shaky internet connections.
Progressive Web Apps are characterized by a Service Worker file, a Web App Manifest, HTTPS, Push Notifications and Background Sync.
Service Workers
A service worker is a script that your browser runs in the background, separate from the web page, opening the door to features that don’t need a web page or user interaction. Service workers are installed on the device from which the website is accessed from and they allow you to control how network requests from your page are handled.
A typical Service Worker process goes like this Register → Install → Fetch → Activation
To install a service worker for your site you need to register it, which you do in your page’s JavaScript. Registering a service worker will cause the browser to start the service worker to install step in the background.
1// Checks if the browser supports Service workers 2 if ('serviceWorker' in navigator) { 3 // registerServiceWorker.js is the file that contains the install, fetch and activativation code. 4 navigator.serviceWorker.register('registerServiceWorker.js') 5 .then(registration => { 6 // Successful registration 7 console.log('Hooray. Registration successful, scope is:', registration.scope); 8 }).catch(function(err) { 9 // Failed registration, service worker won’t be installed 10 console.log('Oops. Service worker could not be installed, error:', error); 11 }); 12 }
The registerServiceWorker.js
file referenced in the code block above usually contains the install, fetch and activation code.
Typically, during the install step, you’ll want to cache some static assets. If all the files are cached successfully, then the service worker becomes installed. If any of the files fail to download and cache, then the install step will fail and the service worker won’t activate (i.e. won’t be installed).
1var CACHE_NAME = 'pwa-cache-v1'; 2 var urlsToCache = [ 3 '/', 4 '/styles/styles.css', 5 '/script/webpack-bundle.js' 6 ]; 7 self.addEventListener('install', event => { 8 event.waitUntil( 9 caches.open(CACHE_NAME) 10 .then(cache => { 11 // Open a cache and cache our files 12 return cache.addAll(urlsToCache); 13 }) 14 ); 15 });
The urlsToCache
is an array of the files to be cached. The cache.addAll()
function requests, fetches them and adds them to the cache.
Now that the service worker has been installed and resources are being cached, it’s expected that the next thing to be done is getting all the cached resources, right?
Whenever a user makes a request (navigates to a page or refreshes a page), the service worker fetch
event is called and this method checks the request and finds any cached results from any of the caches your service worker created.
1self.addEventListener('fetch', function(event) { 2 event.respondWith( 3 caches.match(event.request) 4 .then(function(response) { 5 if (response) { 6 return response; 7 } 8 return fetch(event.request); 9 } 10 ) 11 ); 12 });
In the code block above, the fetch
event is defined, and in the event.respondWith()
function we pass in a promise from caches.match()
. The caches.match()
method looks at the request and finds any cached results from any of the caches your service worker created.
If there is a matching response, the cached value is returned. Otherwise, we return the result of a call to fetch
, which will make a network request and return the data if anything can be retrieved from the network.
The activate
event is usually used for cache management. It is triggered after registering, and it is also used to clean up old caches for newer ones.
1self.addEventListener('activate', function(event) { 2 var cacheWhitelist = ['pages-cache-v1', 'blog-posts-cache-v1']; 3 event.waitUntil( 4 // Get all the cache keys (cacheName) 5 caches.keys().then(function(cacheNames) { 6 return Promise.all( 7 // Check all caches and delete old caches that are not whitelisted 8 cacheNames.map(function(cacheName) { 9 if (cacheWhitelist.indexOf(cacheName) === -1) { 10 return caches.delete(cacheName); 11 } 12 }) 13 ); 14 }) 15 ); 16 });
The code block above is an example of an activate
event. It loops through all of the caches in the service worker and deletes any caches that aren’t defined in the cache whitelist.
After the activation step, the service worker will control all pages that fall under its scope, though the page that registered the service worker for the first time won’t be controlled until it’s loaded again.
Web App Manifest
A web app manifest controls what the user sees when launching from the home screen. This includes things like a splash screen, theme colors, and even the URL that has opened.
1{ 2 "short_name": "PusherCoins", 3 "name": "PusherCoins", 4 "icons": [ 5 { 6 "src": "favicon.ico", 7 "sizes": "192x192", 8 "type": "image/png" 9 }, 10 { 11 "src": "android-chrome-512x512.png", 12 "sizes": "512x512", 13 "type": "image/png" 14 } 15 ], 16 "start_url": "./index.html", 17 "display": "standalone", 18 "theme_color": "#000000", 19 "background_color": "#ffffff" 20 }
From the manifest.json
file above:
short_name
is required for the text on the user’s home screen.name
is required for use in the Web App Install banner.start_url
specifies the URL that loads when a user launches the application from a device. If given as a relative URL, the base URL will be the URL of the manifest.display
defines the developer’s preferred display mode for the web application. It could be standalone
, fullscreen
, browser
, or minimal-ui
.theme_color
defines the default theme color for an application. This sometimes affects how the application is displayed by the OS (e.g., on Android’s task switcher, the theme color surrounds the application).background_color
defines the expected background color for the web application.HTTPS
It’s very important that Progressive Web Apps be served from a secure origin. That’s why it’s a requirement for an application to be served with HTTPS so it can be considered a Progressive Web App. Using HTTPS also ensures that intruders can’t tamper with the communications between your websites and your users’ browsers.
Background Sync
Background sync is a web API that lets you defer actions until the user has stable connectivity. This is useful for ensuring that whatever the user wants to send is actually sent, even after the loss of internet connectivity.
As an example, background sync can be very useful in a chat application. I’m sure you’ve witnessed situations where after a message was sent with a bad connection or no connection at all, it still goes on to deliver the message after a good connection has been established. This is background sync at work.
Implementing background sync in a PWA is very straightforward. Take the code below as an example:
1// Register your service worker: 2 navigator.serviceWorker.register('/sw.js'); 3 4 // Then later, request a one-off sync: 5 navigator.serviceWorker.ready.then(function(swRegistration) { 6 return swRegistration.sync.register('myFirstSync'); 7 });
In the code above, the service worker is registered and then we request a one-off sync with the name of myFirstSync
. Then we listen for the event in /sw.js
:
1self.addEventListener('sync', function(event) { 2 if (event.tag == 'myFirstSync') { 3 event.waitUntil(doSomeStuff()); 4 } 5 });
In the code block above, doSomeStuff()
should return a promise indicating the success/failure of whatever it’s trying to do. If it fulfills, the sync is complete. If it fails, another sync will be scheduled to retry. Retry syncs also wait for connectivity and employ an exponential back-off.
The name of the sync should be unique for a given sync. If you register for a sync using the same tag as a pending sync, it merges with the existing one.
You can read more about background sync here.
Push Notifications
Web Push Notifications are simply a way of allowing users to opt into timely updates from the sites they love and also allow you to effectively re-engage them with customized, relevant content. They work with service workers because of the background usage of service workers.
It’s important to note that Push and Notification use different, but complementary, APIs. Push is invoked when a server supplies information to a service worker; a notification is the action of a service worker or web page script showing information to a user. Therefore, in order for a push notification to work, both a server and a client are needed. You can read more about Push Notifications here.
Progressive Web Apps also rely on some patterns and technologies that help deliver a meaningful user experience regardless of the quality of internet connectivity. I’ll highlight some below.
PRPL
The Push Render Pre-cache Load pattern is a relatively new and experimental pattern that takes advantage of modern web platform features (service workers) to granularly deliver mobile web experiences more quickly.
It is simply a pattern for structuring and serving Progressive Web Apps, with an emphasis on the performance of app delivery and launch. It stands for:
The PRPL pattern is about making sure an application is built in a way that the user gets the best mobile experience by having the lowest possible minimum time-to-interactive (especially on first use) and the best maximum caching efficiency (caching routes and resources).
You can read more about the PRPL pattern here.
Web Storage
Offline support is a key feature of a Progressive Web app, and for offline support to work, some sort of data persistence is needed. This is where Web Storage comes in.
Web Storage allows developers to cater for instances whereby a user temporarily loses internet connectivity. In order to make the Progressive Web App still usable in an instance like that, web storage can be used to display already saved data when needed.
There are various Web Storage APIs available such as Local Storage, Session Storage, and IndexedDB and each comes with its own pros and cons. You can read more about Web Storage on the Google Developers Blog.
HTTP/2
The HTTP/2 protocol is an improvement on the existing HTTP/1.1. It aims to make our applications faster, simpler and more robust. In order to understand how the HTTP/2 protocol improves upon the previous version, let’s compare how a linked stylesheet will be retrieved for use in an application.
In HTTP/1.1:
<link rel="stylesheet">
tag, and starts a new request for the stylesheet.With HTTP/2 push:
<link rel="stylesheet">
, the stylesheet is already in the cache.A powerful feature of HTTP/2 is Server Push. This is the ability of the server to send multiple responses for a single client request. That is, in addition to the response to the original request, the server can push additional resources to the client.
This means the server doesn’t necessarily have to wait for the client to explicitly request some resources. HTTP/2 simply allows the server to push the associated resources ahead of time. These resources can then be cached by the client and reused across different pages.
In this article, we’ve seen what Progressive Web Apps are and how they provide the best experience for users using technologies such as Service Workers, Web App Manifest, Push Notifications and Background Sync.
Technologies like HTTP/2 and Web Storage and the PRPL pattern were also highlighted and the various ways in which they help to deliver the best experience for all mobile users.
I think PWAs are really useful and can help to provide a better experience for users who access the internet through their mobile devices. You can find out more about Service Workers and PWAs with the links below.
In the next tutorial, we’ll see how to build a realtime Progressive Web App by using Service Workers, Web App Manifest, Web Storage, and Pusher.