Progressive web apps can now be installed on Windows, Android, iOS, Chrome OS, and soon on macOS. Is your web app ready?
Chrome, the most popular web browser by far, now prompts Windows users to install progressive web apps (PWAs) to their desktop. The feature has been enabled on Chrome OS for a while now and is scheduled to ship on Chrome for macOS in the first half of 2019. Additionally, the Microsoft Store has included PWAs since April 2018, and they also can be installed to the home screen on mobile devices with good support on Android and some support on iOS.
In other words: PWAs have officially gone mainstream.
The case for PWAs
Installing web apps could be a huge development. They can now be installed to the desktop or mobile home screen without a framework like Electron or Cordova. Under the hood, these apps are powered by the web browser that installed them but they appear to be native applications to the user.
Advantages of installing PWAs include:
- They appear as a separate application in the OS task switcher
- Minimal user interface with no browser controls like back, reload, tabs or the URL bar
- Ability to style the title bar on most platforms
- Launch icon appears alongside native apps, e.g. in the start menu, taskbar and file explorer on Windows
Installable PWAs are ideal for web apps that are used regularly such as productivity tools, games, and dashboards. The apps can make use of the same web APIs as the browser including geolocation, web audio, WebGL and payment request. Twitter, Spotify, and various Google services are just a few of the PWAs that can already be installed.
On Chrome for Windows and Chrome OS a user may be prompted after 30 seconds to install a PWA to their desktop. Alternatively, users can install PWAs from the settings menu.
The next time the user opens the app from the desktop or home screen they’ll see the streamlined PWA interface. Notice the shortcut on the desktop and the taskbar icon.
Other desktop browsers such as Firefox, Edge, and Safari don’t allow users to install PWAs.
Chrome and Opera for Android have supported the installation of PWAs for a while now. The experience is basically the same as on Chrome for Windows and Chrome OS: the user can install the app with through the settings menu or may be prompted by the app itself.
Safari on iOS also supports installing PWAs to the home screen but doesn’t prompt the user. Apple calls them “web clips”. The user has to click the “share” button and choose the “add to home screen” option buried in the menu. This seriously limits the usefulness of the feature on iOS since you don’t necessarily know if a web app is installable or even works offline.
Pseudo-browsers on iOS such as Chrome and Firefox cannot install PWAs.
If your web app is a single-page application it may be straightforward to upgrade it to an installable PWA. In order to see the Chrome install prompt, an app needs to meet the following criteria:
- The app is not already installed
- Meets a user engagement heuristic (currently, the user has interacted with the domain for at least 30 seconds)
- Includes a web app manifest
- Served over HTTPS
- Has registered a service worker with a fetch event handler
While not strictly a requirement, the app should be able to work offline with service worker caching. The app also has to listen for, and handle, the
beforeinstallprompt event. See Google’s Add to Home Screen guide for implementation details.
Case study: WDS-1
I’ve been working on a PWA in my spare time called the Web Drum Sequencer, or WDS-1. It’s a drum machine built with React and the web audio API. You can see it live here and the code is available here.
A couple of features I wanted to include with WDS-1 were to a) make it work offline, and b) make it installable as a desktop app. These features are actually complementary since having a service worker is the most complicated part of making your PWA installable.
Service worker caching strategy
For WDS-1 I knew I didn’t want to pre-cache all of the app resources. Being a drum machine, the app allows the user to load a lot of different audio files. If I were to fetch all of the audio files with the first-page load it would result in a massive page size. Instead, my strategy was to lazy-load the audio files as needed and cache them for future use.
In my service worker script I distinguish between resources fetched from the app’s
assets directory and all other resources.
For resources in
assets, the app checks the cache first and then, if the resource isn’t in the cache, performs a network request. This way the app doesn’t try to load the same audio, image or font file twice.
For all other assets, the app attempts a
fetch first and if that fails because the device is offline it will check the cache. This keeps content such as
index.html fresh while ensuring the app will work offline.
Generate app manifest automatically
Install app dialog
Unfortunately, the PWA install prompt doesn’t appear automatically in Chrome. In order to trigger the prompt, there needs to be some user interaction such as clicking a button.
Taking that into consideration, the installation path I chose for WDS-1 was to display a flash message to the user after 30 seconds. The flash message notifies the user that WDS-1 is installable and gives them a big blue button to install the app.
If the user clicks the “install” button on the flash message, they’ll receive a second dialog window from Chrome confirming that they want to install the app.
I’ll be the first to admit that it’s not a great user experience to have two dialog windows in a row. Nevertheless, Chrome’s two-click scheme prevents malicious sites from tricking users into installing apps while also giving developers some control over the installation process. I anticipate that Chrome will make some tweaks to how this works over time since the current implementation is not very straightforward or graceful, and the W3C specification is still in the working draft phase.
To see the prompt in action open WDS-1 with Chrome version 70 or later on Windows, Chrome OS or Android or enable the
#enable-desktop-pwas flag in
chrome://flags on a Mac.