Turning a website into a mobile app using Progressive Web Apps

Going from website to app … quickly

I recently went through the process of turning my website Idiomatically into a mobile app by making it a Progressive Web App (PWA). It was eye-opening to see the current state of the old dream of “write-once, run-everywhere”.

Back in the year 2000, I was a Sophomore in high school taking a class in Visual Basic. The class material was pretty easy so I spent most of the time building a version of the board game Othello. Once I finished, I read about this amazing technology called ActiveX and with a few easy steps my Othello game was now a website. I was blown away and thought for sure this blurring the line between an Application and a Website would revolutionize the world!

“ActiveX will be the future of the web” — Me in the year 2000

Turns out, the world cared about security and performance and cross-browser compatibility so ActiveX was not that future.

Why PWA?

The goal of Progressive Web Apps is to achieve a similar goal but in a secure and performant way (by leveraging standard web technologies). It allows writing applications that run everywhere with a strong and consistent experience. At its most basic, these apps are just websites with some extra metadata and specific install logic to pre-load and cache files/data for improve performance. The Mozilla page on PWA lists the goals of Progress Web Apps.

There are some key principles a web app should try to observe to be identified as a PWA. It should be:

Discoverable, so the contents can be found through search engines.

Installable, so it can be available on the device’s home screen or app launcher.

Linkable, so you can share it by sending a URL.

Network independent, so it works offline or with a poor network connection.

Progressive, so it’s still usable on a basic level on older browsers, but fully-functional on the latest ones.

Re-engageable, so it’s able to send notifications whenever there’s new content available.

Responsive, so it’s usable on any device with a screen and a browser — mobile phones, tablets, laptops, TVs, refrigerators, etc.

Safe, so the connections between the user, the app, and your server are secured against any third parties trying to get access to sensitive data.

Offering these features and making use of all the advantages offered by web applications can create a compelling, highly flexible offering for your users and customers.

Turning a site into a PWA

After reading about the current state of PWAs my goal was to see how quickly and easily I could turn my site (Idiomatically.net) into a mobile app. I already had a few things working in my favor. The site was already optimized to render well on a mobile device (via use of media queries). It also was built ontop of Create React App (CRA) which helps bootstrap the process. When you create a new web app with CRA you can choose the PWA template which installs a lot of pre-built machinery to make it easy.

npx create-react-app appName --template cra-template-pwa

This creates and registers the PWA manifest and installs several Google WorkBox modules which are libraries designed to make building PWA apps simpler. It auto-generates the service-worker.ts and serviceWorkerRegistration.ts files that are used to setup and run the background processing for the app. Since my site was created without this template (it didn’t exist at the time I started Idiomatically), I created a new app with CRA on the side with that template and copied the required changes into my existing app. You can see my commit for this here. The key things to copy over were

  1. Service-worker.ts
  2. serviceWorkerRegistration.ts
  3. manifest.json
  4. workbox packages into the package.json

Once I had those changes ported into my site I made the follow changes to get it working as a PWA.

Configure the manifest

Each PWA has a JSON manifest that describes the app and provides descriptions, screenshots, colors and icons. Since the goal is to run the app on multiple platforms, each with its own requirements, you need to generate icons of different sizes/resolutions. I used PWABuilder’s Icon Generator to create the icons and add them to my manifest.

Here is a truncated version of the manifest.json:

"$schema": "https://json.schemastore.org/web-manifest.json",
  "short_name": "Idiomatically",
  "name": "Idiomatically - Idioms translated across languages and countries",
  "orientation": "any",
  "categories": [
    "education"
  ],
  "prefer_related_applications": false,
  "shortcuts": [
    {
      "name": "Add an idiom",
      "short_name": "Add an idiom",
      "description": "Add a new idiom to Idiomatically",
      "url": "/new"
    }
  ],
  "screenshots": [
    {
      "src": "static/homePage.png",
      "sizes": "1464x996",
      "type": "image/png"
    },
    {
      "src": "static/addIdiom.png",
      "sizes": "1456x1165",
      "type": "image/png"
    }
  ],
  "icons": [
    {
      "src": "android/android-launchericon-512-512.png",
      "sizes": "512x512"
    },
    {
      "src": "android/android-launchericon-48-48.png",
      "sizes": "48x48"
    }
    /// ... many more ...  ],
  "start_url": ".",
  "display": "standalone",
  "theme_color": "#513b56",
  "background_color": "#e6ecf0"
}

Register the Service worker

The service worker does the heavy lifting of running in the background and downloading/caching files. I include the one line from the PWA template to make this work:

serviceWorkerRegistration.register();

Now when the website loads in the browser, it runs the service worker code from the PWA CRA template which by default caches static content.

Configuring Caching in the Service Worker

With the basic configuration working, there are a lot of things you can do like configuring periodic sync for cached files, caching other types of resources (like web responses) or even configuring push notifications. I decided not to pursue those for my experiment to keep it simple and straightforward.

I only had to make one modification to the service-worker configuration in order to fix a bug in the site login flow. The service worker caching configuration from Google Workbox implements the App-Shell approach which worked well for my site since most of the pages are rendered client side with Apollo. However, it makes an assumption on the URL scheme for links that should not be rendered that way. It wants you to prefix _ (underscore) to paths that should not be rendered locally, but for Idiomatically the paths for authentication are /login and /auth. To fix this I updated my service worker config to ignore those. (I also could have edited those routes to start with underscore instead).

// If this is a URL that starts with /_ or is an auth url, skip.
    const lowerPath = url.pathname.toLowerCase().replace(/\/+$/, '');
    if (url.pathname.startsWith('/_') || lowerPath.startsWith('/auth/') || lowerPath == "/login") {
      return false;
    }

Publishing a PWA into the marketplace

At this point, Idiomatically was running in the browser as a PWA. It caches resources in the background and uses the service worker to intercept web requests. Opening the site in the browser you can see the fancy install button. This adds the app to the dock and allows it to be opened on its own outside of the browser.

Install button!
Yes!

However, we can’t claim victory yet since the goal is to see this app in the app marketplaces. Both Google Play and Microsoft Store support PWA’s distributed as apps, sadly Apple AppStore does not yet.

To build the app package I used that same handy site I leveraged to generate the icons: PWABuilder. This site analyzes your PWA, makes suggestions on how to improve it and make it ready to become a packaged app. It even does the work for you to generate the appropriate app packages.

Nice job to you too, PWABuilder!

After running this analysis and making sure everything was configured correctly, I clicked Build My PWA and followed their steps to get the packaged files for both Google Play and Microsoft Store.

Publishing the apps to the two marketplaces takes time since you must follow each marketplaces’ compliance requirements and go through their required steps. But once done I achieved my goal and had the app’s listing public in both places.

Google Play Listing
Microsoft Store Listing

Mobile Experience

The last question I had was how would it feel as a native app? Even though I did the bare minimum to get my site into the app store, would it still be a good experience? Surprisingly, it felt really good! They don’t hide that its running in a browser (especially since the first time you run it it tells you “Running in Chrome”). But even so, everything works well and is fast and responsive.

Idiomatically.net on an Android phone

I am impressed with how far I got with such little work. I can see the path to making this an even stronger experience by doing more aggressive caching (like pre-cache the AJAX calls) and making use of more of the native browser APIs that interface with the host OS (like making use of the Credential Management API or Notifications API).

Conclusion

PWA has come a long way towards realizing the dream I was after with my Othello app years ago. Write-once and run anywhere seems closer to reality than ever before with a consistent and rich browser experience and both Microsoft and Google putting effort into making this work great in their app platforms. Once (and if) Apple starts adding app support we may finally be able to have our cake and eat it too!