Building a Baseline Progressive Web App - Part 2

Last week, I started the process of converting an online unix timestamp convertor that I built last year into a Progressive Web App. This means that it is now able to be installed onto the home screen of a mobile and act like a native web app.

To do this, I have been following Google's checklist and Lighthouse tool

Check out Part 1 here to get up to speed. As I said there,

The start URL (at least) loads while offline

The recommended way of doing this is with a Service Worker. To be honest, this is a topic that could take an entire post on its own so I won't go into too much detail for how I solve this one.

The first step that I took, was to convert the entire application to be Javascript only. This means that instead of the date conversions requiring extra calls to the server, they all occur on the client only.

The benefit is that only 1 interaction with the server is required. Once I have loaded the page, I no longer need an Internet connection, meaning that I can effectively cache the entire site and all of its functionality.

I tried my best to replicate the current behavior (including the URLs). I used moment.js as the library to do the conversions. There are some minor differences from the previous version in terms of how accepting it is of date formats: PHP was much more forgiving. Still, it mostly works.

The only downside to all this is that I now need JavaScript enabled for the app to work. I used noscript tags to serve up a warning that links to

Once I did this, I could then make a service worker to serve up the page if the Internet is gone.

Again, not going to go into too much detail about exact implementation details. But I used this tutorial to help me set it up.

First I created a file, sw.js in the root directory. This is the actual Service Worker and it is in the root directory so that it has "scope" of the entire website.

Then inside the head of the page I added some code to load it:

    if ('serviceWorker' in navigator) {
        window.addEventListener('load', function() {
                function(registration) {}, 
                function(err) {}

This checks if the browsers supports service workers and if it does, it loads it. You can see there are two functions given as handlers: one for successful registration and one for errors.

Inside the service worker file itself, I have two variables and two functions.

The variables are simple:

var CACHE_NAME = 'cache';
var urlsToCache = [

Simply the name of the cache that I will use later and a list of URLs to cache. I am caching the web folder that contains all of the CSS and JS code as well as the full root path.

Then there is an event listener that is run when the service worker installs:

self.addEventListener('install', function(event) {
    // Perform install steps
    event.waitUntil( {
            return cache.addAll(urlsToCache);

It simply opens a new cache and then adds all of the URLs to it.

The other event listener is where the magic happens:

self.addEventListener('fetch', function(event) {
event.respondWith( caches.match(event.request).then(function(response) { // Cache hit - return response if (response) { return response; } return fetch(event.request); }) ); });

This is run when a request is made and it is where you can "intercept" the request (and response) to do what you want. In this case, I am first checking if the request is previously cached and then I am returning the cached response. Otherwise, I let the request go.

That is it.

So if I run the app from my phone before it had this service worker and turn off the Internet connection, I get the offline screen:

And now, I get the full website. It is operating just like an offline native app.

Page transitions don't feel like they block on the network

For me, this was and easy one because of two reasons. First, once the page is loaded, it doesn't have to make any calls over the network which is the biggest cause of latency. Second, what the code actually does is so simple, that it is super quick.

If I had latency, then they way to cover this point is to throw up some kind of loading screen instantly before any operation takes place.

First load fast even on 3G

Again, because my app is very lightweight, this test passes without any further work.

But ways of speeding it up is by loading scripts asynchronously using the async attribute. You could also make use of the service worker to cache more items (although this says first load, meaning before the service worker has been loaded).

Site works cross-browser

Again, Bootstrap has dealt with much of this and the rest of it is just standard HTML. This was one of the sections that already passed in the earlier test.


At this point, the lighthouse tool is now scoring 100%!

We can call it a day right?

Well, there are still a few small things that fail that don't impact the score but are still good practice and are easy to sort out.

Every image element has an alt attribute Very easy to change. I just have to ensure all of the img tags have an alt tag that describes what they are.

Uses HTTP/2 for its own resources: 9 requests were not handled over h2

This one is interesting.

In case you haven't heard, HTTP/2 is the new protocol standard that is taking over the web. It has loads of new benefits that you can read about here.

My app is served with apache2 which does support HTTP/2. But there are some complications to set up. I followed this blog post to help me set it up.

You basically have to build a version of apache2 with the http2 module and then activate the module.

You can then add to the virtual host files:

Protocols h2 http/1.1

Which says that if the browser supports HTTP2, then server the page using this, otherwise serve the page using normal HTTP. This ensures that backwards compatibility is maintained and you do not need to worry about checking browsers - it is all handled for you in the background.

Note, for HTTP2 to be served, you need to have HTTPS set up (which, if you have been following this guide, you will have by now). This means you only need to add this line to the virtual host that serves the HTTPS request.


As you have seen in these 2 parts, PWAs have the potential to be incredibly powerful. No longer is a there a need to spend a fortune building a native mobile application for every phone OS. Just ensure that your website is progressive enough that user won't be able to tell the difference.

This is the baseline list. There are loads of new APIs currently in development that can bring your web app to next level, using things like Bluetooth and Sensors.

Read the Exemplary list here to bring your app to the next level.

© 2012-2022