This is the second of two design patterns that is entirely Client-Side Rendered (CSR) and supports all the multi-page behaviours.
As before, being entirely Client-Side Rendered means the web application runs in the browser and nothing but static content is served from a web server or CDN; supporting all the multi-page behaviours means the application is shareable, deep linkable, refreshable, history traversable, link operable, and multi-tabbable.
Once again, despite being used on a great many production web applications, I cannot find an existing name for this design pattern, so for the purposes of this blog series I’ll refer to it as the Stateful CSR-MPA pattern.1
How does the Stateful CSR-MPA pattern work?
The Stateful CSR-MPA pattern is basically a combination of the previous two techniques:
- It uses the HTML5 History API to suppress page reloads for all user actions in which they can be suppressed.
- For remaining actions in which page reloads cannot be suppressed, it uses the Simple CSR-MPA pattern to carry through the action and ensure the page reloads are as smooth and efficient as possible.
When you navigate an application using this pattern there are two paths you can take, illustrated in the diagram above: an outer path shown in blue, and an inner path shown in red.
The red path is a kind of shortcut which suppresses page reloads. But you can only take that shortcut in specific circumstances: when you primary-click a link, choose Back or Forward, or select locations from your browser history by long-clicking Back or Forward. In those cases, the application uses the History API to suppress the page reloads that would otherwise occur.
All other actions that involve page transitions take you on the blue path, which reloads the page. When you open the home page for the first time, open an external deep link or bookmark, choose a location from the full History menu, right-click a link and open it in a new browser tab or window, refresh the current page using the Reload button, or even hack the URL directly in the address bar—all those actions take you on the blue path. The blue path works exactly as it did in the Simple CSR-MPA pattern. Everything described there also applies here.
To show it in use, I’ve modified the demo application one last time to implement the Stateful CSR-MPA pattern. Here is a screenshot which links to the modified demo application.
If you open the demo application and perform various actions, it becomes very clear when you’re taking the blue path and when you’re taking the red path:
- If you see the three link colours stay the same: The page did not reload—you took the red path.
- If you see the three link colours randomize: The page reloaded—you took the blue path.
Let’s try it!
Taking the red path…
Try opening the demo application’s home page at https://egalo.com/stat. Then click on any of the three coloured links. You’ll notice that each time you click on a link, your location changes (along with the URL path) based on the colour you selected—but the three link colours stay the same.
This means the page is not reloading: the page’s DOM structure is being preserved, and client-side code is able to hold arbitrary data structures in memory across those page transitions.
You are now taking the red path.
The same happens when you traverse the browser history by clicking Back or Forward, or choose locations from your browser history by long-clicking Back or Forward.
Taking the blue path…
Now try opening a deep link such as https://egalo.com/stat/edf5f5 (“Aqua Haze”). Once you’re there, click the browser’s Reload button. You’ll notice that each time you click Reload, your location says the same—you remain on the Aqua Haze page—but the three link colours randomize.
The page is (obviously) reloading. This means that the page’s DOM structure had to be entirely rebuilt, and any data structures held in memory by client-side code were discarded and had to be reconstructed. However, it happens so fast that you barely notice that a page reload occurred.
You are now taking the blue path.
The same happens when open the home page for the first time, open an external deep link or bookmark, choose a location from the full History menu, right-click a link and open it in a new browser tab or window, or even hack the URL directly in the address bar.
Between the red path and the blue path, we have all the multi-page behaviours covered.
All paths are first-class user interactions
Try not to think of the red path as the primary method of interaction and the blue path as a second-class citizen. This goes back to the principle I stated earlier: a web application that truly supports multi-page behaviours treats them all as first-class user interactions.
As before, if a user wants to right-click a link and open it in a new browser tab, that’s up to them. In this design pattern, that happens to take them on the blue path—but the application makes it happen anyway, smoothly and efficiently. Both the red path and the blue path therefore have primary importance in terms of supporting the user experience.
Embrace page reloads!
One further point to note: while the Stateful CSR-MPA pattern suppresses page reloads for the most common user interactions, it does not suppress them for all user interactions. It cannot.
Unless browsers fundamentally change the way they work, certain actions will always trigger page reloads—even in fully CSR applications. This includes right-clicking a link and opening it in a new browser tab, for example.
There is nothing you can do about this. Any action that takes the blue path described above will incur page reloads. The History API does not make this particular problem go away; it merely reduces the number of situations where page reloads must be handled.
We can therefore say that any web application that supports multi-page behaviours must embrace page reloads.
Any web application that supports multi-page behaviours must embrace page reloads
This does not mean that page reloads are great in themselves—they are not. They are a means to an end. The ultimate goal here is to support multi-page behaviours. But you cannot do that without embracing page reloads.
Multi-page first development
If you’re building a CSR application and you want to support multi-page behaviours, an effective technique is to proceed as follows.
- Design a great URL scheme for your application using Clean URLs.
- Build an initial version of your application using the Simple CSR-MPA pattern.
- Optionally introduce the HTML5 History API and start migrating towards the Stateful CSR-MPA pattern—but only where it’s needed.
I call this approach multi-page first development. It has several advantages. First, it forces you to think about your application’s URL design from the start. Second, your application will have multi-page behaviours designed in, and they’ll be working right from the first prototype. That means there’s no danger of running out of time and omitting multi-page behaviours, or leaving them partially implemented. Third, it forces you to get comfortable with page reloads and making them performant.
Multi-page first development works because of an important property of the Stateful CSR-MPA pattern: it’s not really an alternative to the Simple CSR-MPA pattern but rather an extension of it. It’s basically an optimization—you’re introducing the History API to suppress page reloads for a subset of user actions that would otherwise have worked using page reloads.
You might find that once you reach step 2, you don’t need to add the History API in all cases. Some page transitions might not need it—where the DOM structure of the new page is completely different, for example. If you start out assuming you need to use the History API for every page transition, it might be a case of premature optimization. Also, if you introduce the History API and encounter insurmountable problems with state management, reverting to the Simple CSR-MPA pattern might help simplify things.
The Simple CSR-MPA pattern also has the advantage that tracking user navigation activity works pretty much the same as it does for a traditional MPA using SSR—every page a user visits appears in the web server access logs.
Of course, these points need to be weighed against the smoother page transitions and other benefits you get from using the History API.
Best of all, even if you do proceed to step 3 and end up using the History API throughout your application, none of the work you did in step 2 will be wasted! Your application needs the capability to handle page reloads anyway: to support those user actions where it is inherent, such as opening a link in a new browser tab.
Let’s recap what we’ve learnt so far about the Stateful CSR-MPA pattern.
- It supports an arbitrary number of independently-addressable web pages with unique URLs (the demo application has 16,777,217 web pages, including the home page).
- It requires only a small number of files to be hosted on a web server or CDN (the demo application uses just 4 files).
- It is entirely Client-Side Rendered—nothing but static files are served from a web server or CDN, and all content is dynamically rendered in the browser.
- It supports all the multi-page behaviours—that is, it’s shareable, deep linkable, refreshable, history traversable, link operable and multi-tabbable.
- For regular page navigation, it suppresses page reloads using the HTML5 History API.
- For the remaining actions, where page reloads cannot be suppressed, they are handled quickly and efficiently using the same techniques as in the Simple CSR-MPA pattern.
When you think about it, that’s quite an impressive combination of features to have in a single web application. If you weren’t aware of this design pattern before, it may seem surprising that it’s even possible. But this design pattern, or something like it, has been successfully used in many production web applications in recent years.
But wait… are they Single Page Applications?
Up until now, I’ve avoided using the term “Single Page Application”. There’s a good reason for this. In the next post I’ll address a vexing and potentially contentious question concerning terminology.