• Login
  • Apply
Back to Blog

Turning your existing web app into a PWA

If you are considering refactoring a web app into a mobile one, there are a couple of things you want to assess before rewriting the entire frontend codebase. You might be able to achieve what you are looking for with a much simpler solution, progressive web apps and service workers! So, let’s dive into what a PWA is, how you could implement it and their limitations.

WHAT IS A PWA?

There are many technical definitions on Progressive Web Applications out there, but in the simplest of terms, it’s a website that can be downloaded as an app straight from your browser. It gives you all the perks of building a regular website with the convenience of a mobile application. This also means that it allows your application to send notifications and work in offline mode on all supported devices, much like regular apps1. As long as your website is responsive, your app becomes available for all mobile users, which represent most of the online users today.

ADVANTAGES OF USING A PWA

There has been a lot of transition lately to using PWAs by companies like Twitter and AliExpress3. PWAs have their own advantages as a mobile app. The application is downloaded but not installed, so there’s no need to submit it to any Mobile Application Store. This makes them available to the user in a significantly faster manner, allowing the client to circumvent many bottlenecks in the lifecycle of the product, such as approval waits on updates3.

SERVICE WORKERS

All of this is made possible mostly by the browser’s service workers. They consist of a script that the browser runs to communicate with other servers, much like a proxy server4. For security reasons, service workers only run over HTTPS because they have the ability to intercept network requests and modify responses.

These scripts, much like web workers, allow your browser to run asynchronous, event-based functionalities without interrupting or blocking your application, making extensive use of promises and running in a separate thread from the main4. These workers don’t have any access to the DOM directly, so they’re not able to use the browser’s local storage. For that reason, service workers use the Cache API. The cache storage is what allows you to save relevant files and data and emulate an offline mode.

SETTING UP A PWA

Step 1

First you’ll need to set up a ‘.webmanifest’ file on your directory and add a link in your html to access your web manifest file, similar to what you do with your CSS style sheet, within the ‘head’ tag. The web manifest is a JSON object that will serve as a guide to your browser for the downloaded app. It should contain basic information on your app like name, url, icons, etc;

// manifest.webmanifest

{

  "name":"My_app_name",

  "short_name":"My_app",

  "start_url":"https://myappname.com",

  "display":"standalone", //the preferred display mode (look and feel)

  "background_color":"#fff", //color of the background until the stylesheet is loaded

  "description":"turning my app into a PWA",

  "icons":[

    {

      "src":"./images/icon.png"

    }

  ]

}



// index.html

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

Step 2

The next step you’ll take is to check if the browser supports a service worker and if it does, then register it. There are a couple of ways you can do this step. If you are using vanilla JS, you’ll add an event listener on ‘load’ to run the check and then register the service worker. For the case you are using a framework, like React, you’ll add a script that runs a similar logic to your main html file, outside of the ‘body’ tag;

// index.html

<script>

  // first check if the browser supports the service worker

  if ("serviceWorker" in navigator) {

    // navigator.serviceWorker returns a service worker container, 

    // that has a method to register it to the browser. 

    // Register method returns a promise, which allows you to 

    // use the ‘.then’ or ‘.catch’ syntax

    navigator.serviceWorker.register("serviceWorker.js")

    .catch((err) => {

      console.log("Service worker registration failed");

    });

  }

</script>



It is best practice to update your service worker on every reload of your browser during development mode, so make sure to check that on your browser’s developer tools.

Step 3

Finally you’ll set up your service worker. First, you’ll create a ‘serviceWorker.js’ file in your project’s directory. This file is where you’ll add all of the main functionalities to your service worker. In order to follow the service worker’s lifecycle4, you’ll need three main event listeners. Keep in mind you can always add more to configure your service worker.

3.1 Install

The first event your service worker will be listening to is ‘install’, which is the first thing it does after it’s been registered. Here is where you’ll create a cache in the browser and store all the static files, allowing your app to run offline.

const cacheName = "myOfflineCache";

// list all static files in your project

const staticAssets = ["./", "./index.html"]; 

 

self.addEventListener("install", async (event) => {

  // ”caches” is an instance of the CacheStorage

  // the method “open” returns a promise that resolves to 

  // the cache object matching the cache name

  const cache = await caches.open(cacheName);

  await cache.addAll(staticAssets);

 

  // allow the newly installed service worker to move on to activation

  return self.skipWaiting();

});



Keep in mind that the keyword ‘self’ here gives you access to the service worker’s global scope and the methods and objects available to it.

3.2 Activate

The second event the service worker will be listening to is ‘activate’, which is triggered as soon as installation is complete. Although the it is activated, the application will only use it in the next reload. To make sure the service worker is used in the current session, you’ll have to use the ‘clients’ object’s, which gives you access to methods on the current executable context. By calling the method ‘claim’ the service worker becomes the controller of that context.

self.addEventListener("activate", event => {

  self.clients.claim();

});



3.3 Fetch

Finally, you’ll add an event listener for ‘fetch’ requests, which is where your service worker will either direct the request to the network or to your cache, emulating the offline mode.

For this event, break down the traffic by checking where it's requesting information from. If it's data from your own application, like your static files, serve them from the cache you created before. In all other cases, it’s requiring data from the network and that's when you’ll need to add the conditional statement for the offline mode. This should make your service worker fetch data from the cache in case the network fails to respond.

self.addEventListener("fetch", async event => {

  const req = event.request;

  const url = new URL(req.url);

  // check if the request is requiring data from our own application(location)

  if (url.origin === location.origin) {

  // check our cache

    event.respondWith(checkCache(req));

  } 

  // else, fetch from the network and cache that result

  else {

    event.respondWith(checkNetwork(req));

  }

});

 

async function checkCache(req) {

  // open our cache

  const cache = await caches.open(cacheName);

  // check if there’s data there that match with what the request requires

  const cachedData = await cache.match(req);

  // if there’s data cached, return it, else fetch from the network

  return cachedData || fetch(req);

}

 

async function checkNetwork(req) {

  // open our cache

  const cache = await caches.open(cacheName);

  // try to fetch data from the network

  try {

    // save the fetched data

    const freshData = await fetch(req);

    // save a copy of the response to your cache

    await cache.put(req, freshData.clone());

    // send the response back (returned the fetched data)

    return freshData;

  } 

  // if we are unable to fetch from the network (offline)

  catch (err) {

    // match the request with data from the cache

    const cachedData = await cache.match(req);

    // return the cached data

    return cachedData;

  }

}



It’s important to implement this by checking your browser’s developer tools ‘Application’ tab during all these steps, so you can understand a little better what each of them is actually doing. You’ll be able to see your service worker, the cache storage and the manifest. Once you are done, you can add the application to your home screen by clicking on ‘Add to home screen’ link on your application’s manifest inside the developer tools.

CONCLUSION

As with any engineering solution, there are drawbacks too. PWAs don’t enable you to take full advantage of all the features you can have in a native mobile environment2. This means that it doesn’t provide a user interface with the best possible performance and responsiveness. If that’s what you are looking for, stick to frameworks that are specific to the mobile environment, like React Native.

If you do opt into turning your web application into a PWA, with that simple setup you'll have a downloadable application that can be accessed in any tablet or smartphone and reach all of your mobile users with new amazing features! This setup only allows you to have the offline mode, but remember, there are many other configurations you can add to your service worker to make your app’s user experience even better, like push notifications and periodic background syncs.

References & Useful Links: