Progressive Web Applications have been around for a number of years now, but adoption still seems somewhat slow from what I can tell. Mainly that not many websites ask me to install them as PWA's.
There are alot of advantages that come with PWA's that you won't find on a regular website, and converting your current web apps to PWA's is surprisingly straightforward. In this article I will be breaking down the process from beginning to end to get you started. But first, what exactly is a PWA?
What is a progressive web application
The short simple answer is that a PWA is a website that essentially behaves like an application on your PC or mobile device. You can install the application/website locally and get much of the experience of using a native app.
But aside from the app-like experience, PWA's also offer the following features:
Offline accessibility - By setting up service workers for your website/app you can cache resource files, such as CSS, JS and even images, as needed and serve those files to the client directly from the cache, meaning that technically, your website can load without an internet connection if the need arises.
Improved performance - PWA's are designed for improved performance, mainly because they do rely on cashing resources locally, which reduces the overall bandwidth costs. You could even use your service workers to fetch data continuously in the background, so that by the time a user loads your application, the data will already be in memory.
Home screen install - You can add PWA's to the client devices homescreen, whether that be on a desktop or on a mobile device for easy access.
More security - Progressive Web App's by default need to be served through HTTPS, meaning just an overall better security footprint for both the application and the clients using it.
Push notifications - PWA's can send push notifications to the client, either on desktop or mobile, to notify them of updates on your websites.
Much of what PWA's can do though is actually dependent on the developer and how they've set up their cashing and service worker implementations.
Let's set one up.
1. Setup your manifest.json
First off, the manifest.json file is required for a PWA to work. It's essentially the configuration file for you app, providingng the name of the app, the icons used, behavior and location of the service worker. And this file usually lives in your websites root folder.
Here is a sample manifest file with the various properties listed:
{
"name": "My Progressive Web App",
"short_name": "PWA App",
"description": "A sample Progressive Web App with dummy data.",
"start_url": "/index.html",
"display": "standalone",
"background_color": "#ffffff",
"theme_color": "#3498db",
"icons": [
{
"src": "/icon.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/icon-512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"splash_pages": null,
"scope": "/",
"permissions": [],
"related_applications": [],
"prefer_related_applications": false
}
A few things to note from this configuration. The icons are important, not only because these will be the desktop icons, but because there is a slight requirement to have an icon of a specific size in order to be qualified as "optimized".
You can check how your PWA fares overall in your Chrome inspector under the Lighthouse tab.
Also important to note is that the scope property specifies the base path, or range of URL's, for which the PWA is applicable. So in the example above, the scope of "/" would apply to every page in your web application.
But if you only wanted to target a specific section of your web app, say for example the "app" section, you could specify the scope as "/app/" , in which case the service workers would only have control over the pages following this url path.
2. Create a service worker
A service worker is a script that runs in the background of the web browser and independently (kind of) of your website. It acts as a sort of intermediary between the network and your webpage allowing it to intercept and control network requests. It is a big part of setting up a PWA, because many of the features and benefits mentioned above, such as caching and push notifications happen through the service worker.
Having said that, do note that it is not required for an application to be considered a PWA. If your website is responsive, served over HTTPS and has a manifest.json file, it is technically considered a PWA.
If you do implement a service worker though, here are a few of the added benefits:
Caching files - Service workers can cache assets like HTML, CSS, JavaScript and images from the website onto the users device. This makes loading pages and content much faster than through the network. Though you will have to be mindful of a cache clearing strategy for when you make updates.
Offline rendering - With the files cached (see above), the service worker can then load the web page / app when the user is offline or where the connection is weak. Particularly on a mobile device, this definitely gives users a more "app-like" experience.
Push notifications - Since service workers work independently of your web pages, they can be used to send alerts, reminders and notifications to users who have them installed in the background. This can be done even if the webpage itself isn't loaded.
Background syncing - Again, because service workers run outside of the scope of your web page they can perform operations even when the webpage isn't open. Meaning that your application could run processes in the background to sync data with a server, ensuring that users always have the latest updates even before they load your website/app.
These are the technical features that you have at your disposal, but how you end up using these features is really up to you as the developer.
Create a file called service-worker.js
Let's start by creating a brand new JS file and name it service-worker.js. While you can technically call this file anything you wish, service-worker.js seems to be the common convention found online.
You can place this file in your root directory as well and note that you can not have multiple service worker files per application.
Register the service-worker
Now that you have your file created (should be empty), you need to register that file as the service worker for your application. And you can do that with the following script.
<script>
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/service-worker.js')
.then(function(registration) {
// Service worker registration was successful
console.log('Service Worker registered with scope:', registration.scope);
})
.catch(function(error) {
// Service worker registration failed
console.error('Service Worker registration failed:', error);
});
}
</script>
You'll want to run this script somewhere in your main application so that the browser will register the worker regardless of the page that the user lands on.
Note that the browser will only register the service worker one time, if no service worker is currently found.
Structure of service worker files
There are a few key components to how the service workers are implemented, the main one being the event listeners that define the behavior of your app.
And there are a few key ones to go over, starting with the first one that will get called once a user install your PWA.
Install event
The install event gets triggered once a user has, you guessed it, installed your PWA. It pretty much only gets called the one time, and for that reason, it's actually pretty important.
// Service Worker Installation
self.addEventListener('install', (event) => {
event.waitUntil(
caches.open('my-cache').then((cache) => {
// Caching static assets during installation
return cache.addAll([
'/',
'/index.html',
'/styles.css',
'/script.js',
'/images/logo.png',
]);
})
);
});
This event is commonly used to cache static files, such as CSS, JS and images. Though any type of initialization that you need to perform can happen here as well.
activate event
The activate event is triggered when a new version of the service worker gets activated and it can look something like the following:
self.addEventListener('activate', (event) => {
event.waitUntil(
// Perform activation tasks here
cleanupOldCaches()
);
});
function cleanupOldCaches() {
return caches.keys().then((cacheNames) => {
return Promise.all(
cacheNames.map((cacheName) => {
if (cacheName !== 'my-cache') {
// Delete old caches (except for the current cache)
return caches.delete(cacheName);
}
})
);
});
}
In this particular example, we call the waitUntil method to make sure that the activation tasks are completed on the browser. And the main function called, cleanupOldCaches(), essentially iterates through every item in the cache currently and then deletes them.
And if you're wondering when exactly a service worker gets activated, its mainly when the browser detects that there's a new version of the worker that it needs to load. So if you've made changes to your service-worker.js file, then the browser will need to activate this new version.
fetch event
The fetch event gets triggered whenever a network request is made from one of the web pages controlled by your service worker. In this event you can either serve cached content, or content from the network, or even modify the content before it gets back to the user.
self.addEventListener('fetch', (event) => {
// Respond to fetch requests here
event.respondWith(
caches.match(event.request).then((response) => {
// Check if the requested resource is in the cache
if (response) {
// If it's in the cache, return the cached response
return response;
} else {
// If it's not in the cache, fetch it from the network
return fetch(event.request)
.then((networkResponse) => {
// Clone the network response to store in the cache
const responseToCache = networkResponse.clone();
// Open the cache and store the network response for future use
caches.open('my-cache').then((cache) => {
cache.put(event.request, responseToCache);
});
// Return the network response to fulfill the request
return networkResponse;
})
.catch((error) => {
// Handle fetch errors here, e.g., show a custom offline page
console.error('Fetch error:', error);
});
}
})
);
});
This is overall a very basic example of the potential usage of the fetch event and your mileage will vary.
But essentially, the script is checking if the requested resource is in the cache already. And if it is, then it will return this cached response.
If it's not in the cache however, it will add it to the cache first and then return it to the user.
3. Make your site responsive
Because PWA's are designed to function more like apps than websites, responsiveness is important. Whether a user is installing your application from their browser, or from their tablet or their phone, you'll want to make sure that the experience feels like an app.
In this article, we won't go into detail on how to make sites responsive, but for now ensuring that all content loads appropriately on different viewports should be enough to launch an MVP.
In conclusion
Converting your current websites into Progressive Web Applications is in itself a simple and quick process, but determining just how you're going to leverage these new capabilities can take some time.
Slowly but surely more and more websites will begin their transition into hybrid website/apps in the coming years. But it's definitely good to stay ahead of the curve while you can.