Posted 4 min to read

PWA (Progressive Web App) is a web application that in theory works in a similar way to a native application installed on a desktop or mobile device. In this article you will learn how to make your application meet all the minimum PWA requirements in 15 minutes and make it installable.

HTTPS

The first requirement is to serve your website via HTTPS. This is necessary to register a service worker. The most popular and free method is to use certificate from Let's Encrypt.

Manifest

This is a JSON file that determines the behavior of the application after its installation. First of all it should contain the application name, icon paths and start URL. The complete specification of this file is available on the W3C website.

The example manifest.json file for the PWA application might look like this:

{
    "name": "Smakowity.pl",
    "short_name": "Smakowity.pl",
    "description": "Food recipes and articles.",
    "icons": [{
            "src": "/android-chrome-192x192.png",
            "type": "image/png",
            "sizes": "192x192"
        },
        {
            "src": "/android-chrome-512x512.png",
            "type": "image/png",
            "sizes": "512x512"
        }
    ],
    "start_url": "/?source=pwa",
    "display": "standalone",
    "scope": "/",
    "background_color": "#FFF",
    "theme_color": "#FFF"
}

Also, you have to add link to the manifest in the <head> section:

<link rel="manifest" href="/manifest.json">

Service Worker

Actually, this is a very important JavaScript file that is responsible for handling HTTPS requests and returning responses. Its main idea is to provide offline capabilities to the application. All requests made by the application go through it, including those to other domains!

Create service-worker.js file in the application's public directory and define the cache name and URLs to the content that should be available offline.

const CACHE_NAME = 'smakowity-v1';
const URLS_TO_CACHE = [
    '/offline.html',
    '/images/logo.png'
]

Next you need to define three events within which the service worker does its work: install, activate and fetch.

The "install" event

It is called after the service worker is registered. This is where URLs to be cached are usually added.

self.addEventListener('install', function(event) {
    event.waitUntil(
        caches.open(CACHE_NAME)
        .then(function(cache) {
            return cache.addAll(URLS_TO_CACHE);
        })
    );
});

The "activate" event

It is called after the service worker is installed. This is a good place to clear the cache from old data used in the previous version of the script.

self.addEventListener('activate', (e) => {
    e.waitUntil(
        // Get all available caches
        caches.keys().then((keyList) => {
            return Promise.all(keyList.map((key) => {
                // Delete old caches
                if (CACHE_NAME.indexOf(key) === -1) {
                    return caches.delete(key);
                }
            }));
        })
    );
});

The "fetch" event

It is called when a HTTP request is made in both online and offline mode.

self.addEventListener('fetch', event => {
    // Handle requests only from the app domain and pass the rest
    if (event.request.url.startsWith(self.location.origin)) {
        event.respondWith(
            caches.match(event.request).then(cache => {
                // If the requested resource is in the cache
                if (cache) {
                    return cache;
                }

                return caches.open(CACHE_NAME).then(cache => {
                    // Return the offline.html page in the offline mode
                    if ( ! navigator.onLine) {
                        return caches.match('/offline.html');
                    }

                    return fetch(event.request).then(response => {
                        return response;
                    }).catch(error => {
                        console.warn(error);
                    })
                })
            })
        );
    }
});

In the example above, the content of the offline.html page is returned in the offline mode, which may contain information for the user to go online in order to continue using the application. This allows us to meet the necessary requirement of returning the 200 HTTP code for every URL of the application in offline mode. Please note that all elements of the site template (CSS, JS, graphics etc.) should also be in the cache (must be defined in the URLS_TO_CACHE constant).

This is of course very simple offline support. The assumption is that the PWA application should be functional in this mode as much as possible (i.e. it should display the entire interface and synchronize data after going online).

Service Worker registration

Add the following code to the main JS file:

if ('serviceWorker' in navigator) {
    navigator.serviceWorker
        .register('/service-worker.js')
        .then((registration) => {
            console.log('Service Worker has been registered with scope: ' + registration.scope);
        })
        .catch((error) => {
            console.error('Service Worker registration failed: ' + error);
        });
}

Responsiveness

The website must be responsive and adapted to mobile devices. You should at least add a meta viewport tag to the <head> section and adjust the displayed content to the width of the viewport.

Example code:

<meta name="viewport" content="width=device-width, initial-scale=1">

Lighthouse audit

The correctness of PWA implementation can be checked using the Lighthouse tool, which will indicate what else should be improved.

PWA audit in Lighthouse

Google has also prepared the PWA checklist, which is worth reading in the context of expanding PWA with optional features.