Building a Running Calculator PWA in React and TypeScript
I have been an avid runner for many years now, but I still get confused by imperial and metric measurements. On top of this, the amount of times I seek out a random website to perform some form of pace/distance calculation is too often to count. What I wanted was a desktop and mobile application that could perform pace, distance and time calculations, along with imperial/metric conversions - a swiss-army knife of running calculators. In this article I would like to document my experience building a Progress Web Application (PWA) which does just this; providing a native app-like experience across iOS, Android and Desktop.
The resulting application has been built using React, TypeScript, CSS-in-JS (styled-components) and bundled with Vite. I have leveraged the Vite PWA plugin to abstract away generating the required Service Worker, Web app manifest and icon assets to meet PWA requirements. Under-the-hood Google’s Workbox project is used to handle managing caching assets for offline use and updating the registered Service Worker.
When initially researching PWA development, you would naively assume it is just a case of adding a Web app manifest and Service Worker to your project, serving the content over HTTPS and you’re done… not quite. Below I will discuss the different areas I needed to consider which lead to final result shown above.
Look and Feel
When the iPhone was initially released, Steve Jobs toted an innovative new way to create web applications “that look exactly and behave exactly like native apps” during the launch’s one last thing section. However, the momentum soon dwindled with the release of a native SDK several months later - and the subsequent popularity/revenue of the App Store has not made Apple regret that decision. This has lead to much of my PWA development experience sadly feeling like how can I make this work on iOS… (and this is coming from an Apple fanboy). iOS is still way behind the awesome web capability support that is present on Android, for example it has only finally added Web Push capabilities since iOS 16.4.
Below are several of the key pain points I have felt whilst developing this PWA on iOS.
The amount of splash-screen assets that are required to be generated to provide full iOS device support is mind-blowing. Not only do you need to supply many assets up-front, iOS requires custom Safari specific meta-tag workarounds to register the different splash-screen variants. This is in stark contrast to Android where the system is able to use an existing icon provided by the Web app manifest.
iOS lacks support for the Web app manifest orientation (portrait and/or landscape) property, which allows you to specify what orientations the PWA supports. Instead you are required to cater for both, or provide crude CSS-based ‘Not support in this orientation’ messages to the user.
Most annoying of all, iOS lacks native installation prompts, which on the other hand is fully supported on Android and Chrome desktop devices.
It does not support the
beforeinstallprompt event which means that to install a PWA you have to instruct the user to manually visit Safari, open the bottom menu bar and ‘Add to Home Screen’ 🤦.
Third-party browsers (i.e. Chrome) although being required to use the Safari web-engine do not have support for installing PWAs.
This really puts PWA adoption on the back foot compared to the seamless experience that iOS provides for App Store installations.
Android on the other hand provides a great user experience, harnessing screenshots found in the Web app manifest to aid the user in installing your application.
It even packages up the application as a Trusted Web Activity, which treats the PWA as if it were like any other Android application, not just a Safari bookmark icon.
You can see how the two experiences differ by watching the videos above.
In contrast to the work required on mobile to meet the desired look and feel, desktop development did not require as much attention. As PWAs support is only present within Google Chrome (and variants such as Microsoft Edge), this limited the scope of browsers that needed to be catered for. What is great about this approach is that there is no need to wrap your application in an Electron/Tauri wrapper if the APIs supplied by Google Chrome meet your needs.
I am very happy with the PWA that I have been able to build over the past week or so. Although the application itself is simple, it has allowed me to explore many of the fundamental areas of PWA development that need to considered when building web applications that should look and feel native. iOS support, although lacking behind Android considerably, at least provides a native look and feel once it has been installed on to the device. To help aid in this issue, going forward I would like to explore how I can use projects such as PWABuilder to package the PWA into an application that can be submitted onto both the Google Play and Apple App Store.