Why Progressive Web Applications Are Not Single Page Applications
For some reason many think progressive web applications (PWA) are single page applications (SPA) or more commonly framed by marketing folks, JavaScript applications.
This is wrong, very wrong.
A SPA can be a PWA, but a PWA does not have to be a SPA.
I recommend against being a single page application for sites targeting organic search traffic and for most online solutions.
Why?
Because client-side JavaScript does not provide the better user experience developers think they do. In fact, they create a relatively poor user experience in most cases. And no, your developers probably are not the exception that makes good SPA user experiences.
Requirements to be a single page application are not the same requirements to be a progressive web app. This means you can make a SPA, but it is not a PWA and vice versa.
So why do so many developers and marketers assume a progressive web application is a single page application?
Could the Google Chrome Team be the cause of this confusion?
Is it just the overwhelming popularity of what I call 'fast food' JavaScript frameworks?
And, what is the best type of website for a PWA?
- What is a Single Page Application
- Why Progressive Web Apps Kill Single Page Apps
- Why Progressive Web Apps are not Single Page Apps
- Why the Chrome Team May Have Created PWA is a SPA Misconception
- My Recommended Progressive Web Application Website Model
- Wrapping It Up
What is a Single Page Application
Single page applications are JavaScript heavy websites that by pure definition rely on a single web page with client-side, dynamically rendered markup. Instead of loading individual pages (HTML) completely from the server, either partial markup or data is loaded from the server and composed in the browser before rendering markup in the DOM.
The primary draw many of us had to use single page applications was the desire to build websites to feel more like native applications.
Without the traditional round trip between the browser and the server for the entire page the app shell remains visible in the browser while the content is being retrieved and rendered.
In other words, server-side rendering is moved to the client.
To do this a single page application leverages a JavaScript based rendering engine and AJAX. This rendering is triggered either by a change to the URL by changing the hashfragment, the value after a #. Another event is a purely JavaScript driven change, typically due to a use action like clicking a button.
Instead of retrieving full pages either partial pages or just data is retrieved using a network call behind the scene. This has been done using AJAX, but today has changed to 'fetch'.
The primary page is the 'app shell', or the minimal amount of markup to compose what we refer to as the application chrome. This typically means the layout's header, footer and primary navigation.
Like anything developers touch they tend to throw excessive amounts of code at a solution. This is why single page applications evolved out of the jQuery era into giant, code heavy, 'fast food' JavaScript frameworks.
Popular examples are Angular and React, but there are several others that either failed to garner mass popularity or are currently emerging. There has probably been a new one released as I wrote this article!
I call them fast food frameworks because they are full of over processed code and syntactic sugar with a lack of technical nutrients. Just like actual fast food is full of processed carbs and sugar that makes many of us obese!
Unfortunately the nature of these frameworks appeals to the average developer because they are code (JavaScript) heavy. They also give the illusion of abstracting the JavaScript away or convenience. This is similar to the way we think fast food restaurants save us time and money.
This perceived convenience and cushion of excessive code makes these frameworks an irresistible force to most developers.
But the choice of these frameworks comes at a cost and that is paid in poor user experience and reduced customer engagement or productivity.
Back to the main point, Single Page Apps can be Progressive Web Apps, but PWAs do not have to be SPAs.
That's because a single page app does not require a service worker, web manifest file or being served via HTTPS. They also fall short in the spirit of progressive web apps where the ultimate goal is to deliver amazing user experiences.
Why Progressive Web Apps are not Single Page Apps
Progressive Web Applications are websites that meet three technical requirements:
- Served using HTTPS (Secure)
- Have a Valid Web Manifest File with a Minimal Set of Icons
- Register a Service Worker with a Fetch Event Handler and Minimal Offline Support
No where in those requirements does it say the PWA should not request server rendered pages or that content must be rendered in the browser.
There is also nothing in those requirements saying the website must use JavaScript. Well sort of, you must register the service worker using client-side JavaScript
This is all the JavaScript you must include in your web pages to register a service worker:
if ( "serviceWorker" in navigator ) { navigator.serviceWorker.register( "/sw.js" ).then( function ( registration ) { // Registration was successful console.log( "ServiceWorker registration successful with scope: ", registration.scope ); } ).catch( function ( err ) { // registration failed :( console.log( "ServiceWorker registration failed: ", err ); } ); }
Your site's service worker is written using JavaScript, but it executes in a different process so does not count against your front-end user experience. Speaking of UX...
There are some user experience expectations progressive web applications have, such as speed, engaging and integrated. These are measured more in the eye of the beholder, but again nothing about client-side rendering.
As progressive web applications grow in demand developers naturally want to retain their love of single page applications, even when they are no longer needed.
Wait, did I say single page applications are no longer needed?
Let's dive into this juicy position.
Why Progressive Web Apps Kill Single Page Apps
Several years ago I was anxiously awaiting my first presentation at O'Reilly's Velocity on building fast single page applications watching Patrick Meenan demonstrate service workers. As I watched Patrick I could not help but think everything I was about to show was a polyfil.
I was blown away by this new web platform feature, service workers, because I could see how I architected my single page applications now as a native or built-in browser feature!
I architected my single page applications differently than Angular and React have designed their application structures. My architecture was built with user experience the primary feature requirement. Fast food frameworks are designed to make the developer experience the primary goal.
A simple rule of thumb is, the more client-side JavaScript, the worse your user experience.
Browsers must load the file across the network, decompress the script (assuming you use compression, which you should) parse and execute the script before it can continue with the rendering process. That's because JavaScript is a blocking process.
If the JavaScript changes the DOM, the critical rendering path must recycle, delaying content rendering.
There is more to the process, but I don't want to get side tracked. Just accept the fact JavaScript makes your pages render slowly, even when within the context of a single page application.
So how do PWAs kill SPAs?
A properly designed progressive web application pre-caches many site assets: scripts, css, images and markup, before they are required. This eliminates the network from the rendering process and enables the application to work offline.
A PWA can still use the app shell model, but now instead of the core markup being rendering in the UI thread it can be prefetched in the service worker or dynamically rendered in the service worker.
The key to this process is the service worker runs in a separate thread from the UI. This means the browser tab is not locked up while the JavaScript is executed to render the content.
This means you can completely remove the excessive JavaScript overhead single page application JavaScript frameworks add to your webs apps. Instead you can move that logic to the service worker.
I demonstrated this concept earlier this year when I published a PWA for the Philly Code Camp. I dive even deeper into using service workers to manage content rendering in my new Progressive Web Application development book.
Following this model, I have found I can reduce my JavaScript footprint to less than 10kb in many cases, not megabytes required by frameworks and their extension ecosystem.
Why the Chrome Team May Have Created PWA is a SPA Misconception
The Chrome team is great, they provide lots of great content to help developers be more productive and produce better quality web pages. And while their intent is noble it can be misunderstood.
The Chrome team knows there are many sites built using fast food frameworks. They try to be helpful to give developers some guidance on how to make these frameworks work better.
One of those areas is the concept of an 'app shell'.
For single page applications I like to think of the app shell as the primary canvas or context to paint the application, driven by user actions to render updated content.
…… …
Anyway, the Chrome team has several resources around the concept of the app shell and they do a great job showing how to use this model.
What I hate about this though is many developers and site owners consume this content and assume it is the way to build progressive web applications.
This is wrong.
Using an app shell is a way to drive a progressive web application, but not a requirement.
In fact, I think it may be a bad way to make progressive web apps.
When you use proper service worker caching techniques you should not need an app shell because your pages tend to come from a locally cached response, which when designed correctly should render in a few hundred milliseconds, not 22 seconds!
Why 22 seconds?
That is the average time it takes the average web page to render on an average client device according to Google's research.
The average time it takes to fully load the average mobile landing page is 22 seconds. However, research also indicates 53% of people will leave a mobile page if it takes longer than 3 seconds to load. Mobile Page Speed Benchmarks
Let me be clear, you cannot pre-cache every page of every website. Some sites you can, but not all.
I always use Amazon as my go to example. That's because they have millions of product pages and caching their entire site on a phone or any client computer is just impossible and of course irresponsible.
In these scenarios the use of an app shell is a good idea. But instead of using a single page application you can leverage your service worker to cache page templates and retrieve each page's JSON on demand and render the page in the service worker.
While you won’t have an 'instant' page rendering experience the use of the app shell helps offset any anxiety due to network and JavaScript framework latencies. Plus you can locally cache the result for future requests of the same resource.
The main advantage to leveraging the service worker is not needing a JavaScript framework or a single page application to drive this experience. A SPA is congruent to the overall desired goals of the page.
My favorite solution is to leverage a simple template engine, like Mustache, and cached content templates to render pages in the service worker. Or have partial pages pre-rendered on the server and just fetch them. Once the page's content is available you can then push it to the client and make a simple document.querySelector("target-element-selector").innerHTML = [page content HTML].
Without the framework overhead this process feels 'instant' even on average mobile phones.
So a key performance issue is addressed with service workers, but what about links?
Solving Single Page App Linking Issues
The web's primary super power is the ability to deep link. Single page applications tend to use hash fragments, look for the #! in a URL. The hash fragment value triggers a client-side event uses by these frameworks to drive the rendering process. But that hash fragment value is not passed to the server.
In addition to the hash fragment not registering on the server search engines do not index content based on hash fragments.
There has to be a PWA 'best practice' or type of web site, right?
This is where the static website with real URLs wins.
Imagine that, web pages with real addresses solve many problems created by single page applications.
Even if the browser does not support progressive web apps and service workers, which is not many these days, static pages will just work.
My Recommended Progressive Web Application Website Model
If a Progressive Web Application is not a single page application, what is it?
It’s simple, a website that uses the best the web has to offer to deliver great user experiences.
So what is the best the web has to offer? Well that depends on what a site needs.
Some applications need more hardware features, others rich animations or media integrations. But almost all websites have a core common set of expectations.
- Load Fast
- Smooth Animations
- Responsive, Mobile First Design
- Some Dynamic Rendering Capabilities
In the past I was a champion of single page applications, but I can't be anymore. Today I am a progressive web application champion, the natural progression of SPAs.
No surprise there, but this might surprise you:
I now recommend using a static website.
That's right, the original type of website, static and boring.
But unlike when the web originally debuted these static sites are not 100% static, in fact they are not even on a web server.
Intrigued?
I use server-side rendering, but I utilize a series of AWS Lambdas that use nodejs to produce static markup when the page's source is created or updated. In other words, the markup is only rendered when the content changes, not on demand and not in the client.
These pages are 'hosted' in AWS S3 and utilize the AWS CloudFront CDN.
I can't tell you how much simpler this configuration is to manage than I used to deal with when using IIS and other web server platforms.
However, there are scenarios when a site might need to be rendered on demand.
For example, when content is driven by an authenticated user's profile. A good example of this is when a site header is personalized for an authenticated user, instead of showing a login button.
There are thousands of other 'common' scenarios that could be argued to drive a think JavaScript or SPA experience. Most could be disproven as a means to justify a single page application and heavy browser-based rendering.
It ultimately comes down to how well you can finesse these scenarios. If I do need more client-side rendering I am going to limit the scenarios more to what might be called a SPAlet.
This is where a page is responsible for creating an interface to manage or interact with a collection of closely related data. I look at this model as a way to throw back to the original inspiration I had to create early single page apps, a list with a modal dialog to edit records.
Ten years ago I started using jQuery and jQuery UI to design pages. One of the first things I did was modify my forms over data strategy to render data in tables with an edit and new record button.
When a record was selected I would display a modal dialog (in many cases) to enter and update the record data. When the dialog was submitted the data would be posted to the server and the list updated.
This simple model can be applied to many line of business scenarios and even some consumer use cases. But you don't need to manage every single view or page needed in an application in a giant set of client-side JavaScript.
Wrapping It Up
Unfortunately many seem to be caught up by the popular misconception that progressive web applications are single page apps. While they can be SPAs they do not have to be SPAs.
In fact you are better served by avoiding single page applications today. Instead use service workers to implement much of what single page applications were designed to do. But instead of clogging the UI thread with excessive JavaScript you can offload client-side rendering tasks to the service worker.
Also, evaluate what pages in your site are candidates for service worker rendering. Not every page is a good candidate for client-side rendering. You can leverage service worker caching to eliminate the network request for many pages as well as needing heavy JavaScript payloads.