Progressive Web Applications

There is no single definition of a PWA. The web tradition has always been to offer functionality in a progressive and layered approach. Let's peel back these layers, and see what makes a PWA. 5 September 2022

# Progressive enhancement

The web has long held a tradition of progressive and layered enhancement. Browsers already have native ability to interact with server rendered HTML; it can visit links and submit forms back to the server. Javascript progressively enhances that experience by dynamically reshaping the DOM on user events. With service workers, browsers can handle intermittent network errors. A manifest allows web applications to be installed much like native ones. More exotic APIs, like the Notification API allows web applications to send OS-native notifications, and the Push API allows web application to receive live updates from the server.

Let's look at these in turn.

# Before Javascript

When presented with HTML, browsers can parse and render it even whilst it is being downloaded/generated. Indeed this is the fastest way to display data from the underlying service layers; there is no need to wait for all the data to be available before any of it can be consumed. Sending HTML over the wire (instead of JSON), allows the browser to do so with very little effort. Related assets like stylesheets, images, etc. are automatically downloaded as references to them are streamed in, and are downloaded with the relevant priority. Browsers truly are very clever on their own.

Browsers can navigate from page to page by following <a href> links. They can provide feedback to the server through form POST or GET, following redirects as necessary. Backward/forward navigation is natively supported, and cached in most instances. Provided the markup is semantic, accessibility is also a first class citizen.

All of this is available natively without any Javascript.

# Javascript delighters

With Javascript, the DOM can be dynamically reshaped with widgets appearing/changing/disappearing on user events, form submissions can be automatic at opportune moments, and much more.

Used this way, Javascript is a delighter, and not mandatory for the experience. Of course, some behaviors require Javascript in order for it to work at all, but core business functionality should always be available without it. For example, an e-commerce site should allow users to add items to cart, modify the cart, and complete the transaction, without Javascript. Only for auxiliary functionality, like image carousels, image zooming, etc. can Javascript become a requirement.

However, in recent times, many web applications are littered with event.preventDefault(), hijacking default browser behaviour with custom behaviour. Single Page Applications hijack the browsers back/forward history behaviour, introducing their own. Form submissions default to JSON or worse, GraphQL, which means Javascript is mandatory everywhere.

# Intermittent networks

With variable mobile network conditions, there is increasing need for web applications to tolerate intermittent network failures. A user on a train, for example, can experience random dropouts in connectivity as they are travelling. Connectivity may be lost in one interaction, but by the next interaction, it could already be restored. Web applications can smooth out the jarring experience with service workers.

The service worker cache is an additional network-independent layer that can be used to pre-cache critical or previously visited resources. Judicious use of it allows graceful fallbacks to be presented when the network is temporarily unavailable.

Note, this is not a performance optimisation technique. In principal, it is no different to clever use of <link rel="preload"> to pre-warm a cache. More on performance shortly.

# Installable

Finally, web applications can be installed, much like native applications can be. To achieve this requires the Web Application Manifest . This is a JSON file that specifies application descriptors like colors and iconography. Having web applications thus installed provides convenience to the user and can result in increased re-engagement.

# Other APIs

To provide further integration to the OS, there are several other optional APIs. For example, the Notification API  allows web applications to display OS native notifications. Note that this doesn't require that the application to be installed, it can send notifications with it running in a background tab too. It does however, require user consent to work.

The Push API  allows services to push notifications to installed clients. It is usually combined with the Notification API mentioned earlier.

Various other APIs to use device capabilities can also enhance the experience.

# Graceful degradation

The corollary to progressive enhancement is graceful degradation. Virtually all web applications function fine without having to be installed. They also function fine if the service worker functionality isn't available (provided the network is functioning correctly).

But unfortunately, the vast majority of web applications stop functioning without Javascript. Javascript has become a essential part of the experience, rather than a delighter. This is due to the proliferation of client-side rendering libraries like React. But Javascript isn't always available about 1% of the time , and through not fault of the user. Curiously, as application authors, we tend not to tolerate such a failure rate in any other service, yet somehow we allow it in frontends.

# A word on performance

It is a myth that PWAs increases performance. More likely performance improvements due to any rewrite is due to the removal of cruft and rethinking old bottlenecks than the PWA itself. Service workers pre-cache resources. While this saves on the network transfer of said resources, at a network level, it is no different to judicious use of <link rel="preload">. The browser still needs to parse and evaluate that Javascript at all.

I repeat, service workers help smooth out the intermittent network, it does not offer any real performance improvements.

To deliver real performance improvements, you need to consider how you load your data. Complicated data lookups require multi-table or multi-service joins. If the client resolves these long chains of dependencies, every chain involves a network hop and is very expensive. If the server resolves these, then pass the data for the client to render, there is possibly nothing to show until everything is ready to show.

The solution is to render on the server progressively as the data is being resolved. This way, fast data is streamed to the client quickly, while slow data is streamed when it is ready. Serendipitously, this strategy of sending HTML over the wire also means less Javascript in the frontend, less to parse and evaluate on the browser, and also less to develop in the first place!