The Stateful CSR-MPA pattern


This is part 4 in a series on multi-page behaviours

In this post I’ll cover the second of two design patterns that is entirely Client-Side Rendered (CSR) and supports all the multi-page behaviours. Once again, despite being used on a great many production web applications, I can’t find an existing name for it—so for the purposes of this blog series I’ll refer to it as the Stateful CSR-MPA pattern.1

Full CSR-MPA pattern

The Stateful CSR-MPA pattern

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, and supporting all the multi-page behaviours means the application is shareable, deep linkable, refreshable, history traversable, link operable, and multi-tabbable.

How does the Stateful CSR-MPA pattern work?

The Stateful CSR-MPA pattern is basically a combination of the previous two designs:

  • 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, shown 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 involving page transitions take you on the blue path, which initiates a full page reload. 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 this pattern in use, I’ve modified the demo application one last time to implement the Stateful CSR-MPA pattern. Here’s a screenshot which links to the modified demo application:

Stateful CSR-MPA pattern demo application

Stateful CSR-MPA pattern — 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 get randomized: The page reloaded—you took the blue path.

Let’s try it!

Taking the red path…

Open 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 open 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 are randomized.

The page is reloading (obviously, because you’re clicking Reload). 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, all the multi-page behaviours are supported.

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. Using this design pattern, that will 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!

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 a page reload. The History API does not make this particular problem go away; it merely reduces the number of situations where a page reload must be handled.

Any web application that supports multi-page behaviours must therefore 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, and designing for, page reloads.

Multi-page first development

If you’re building a CSR application and you want to support multi-page behaviours, a good way to proceed is as follows.

  1. Design a great URL scheme for your application using Clean URLs.
  2. Build an initial version of your application using the Simple CSR-MPA pattern.
  3. Optionally introduce the HTML5 History API and start migrating towards the Stateful CSR-MPA pattern—but only where necessary.

You can think of this as multi-page first development. It has several advantages. First, it forces you to think about your application’s URL design right from the start. Second, your application will have multi-page behaviours designed in, and working from the first prototype. That means there’s no danger of running out of time and omitting multi-page behaviours. Third, it forces you to get comfortable with page reloads and how to make 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 well (albeit a little slower) using page reloads.

You might find that once you reach step 2, you don’t need to add the History API in all cases—using it everywhere might be a case of premature optimization. But even if you do use the History API throughout, the work you did in step 2 will not be wasted, because your application needs the capability to handle page reloads anyway to support the cases where it’s unavoidable, such as opening a link in a new browser tab.

A recap

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.


  1. I’m using this name not because this pattern is completely stateful and the previous one is completely stateless—but rather because this pattern is comparatively more stateful than the previous one