The Stateful CSR-MPA pattern


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

In this post I’ll describe 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; while supporting all the multi-page behaviours means the application is shareable, deep linkable, refreshable, history traversable, link operable, and multi-tabbable.

This design pattern is basically a combination of the previous two design patterns:

  • 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 page reloads are as smooth and efficient as possible.

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 following diagram illustrates how the Stateful CSR-MPA pattern works.

Full CSR-MPA pattern

The Stateful CSR-MPA pattern

In this design pattern, there are two paths you can take as you navigate the application: an outer path shown in blue, and an inner path shown in red.

Overall, it resembles the Simple CSR-MPA pattern—the difference is that we’ve now introduced a shortcut, the red path. But you can only take that shortcut in specific circumstances: namely, 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 Reload, 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.

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 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.

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 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, 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 actions such as 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.

  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 slowly migrate towards the Stateful CSR-MPA pattern—but only where it makes sense.

I call this approach 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 they’ll work from the first prototype. That means there’s no danger of running out of time and omitting them, or leaving them partially implemented. Third, you’ll get comfortable with page reloads and learn 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 using page reloads.

You might find that once you reach step 2, you don’t need to proceed to step 3 in all cases. For some page transitions, the History API might not be necessary—where the DOM structure completely changes, for example. To start out assuming you need to use the History API for every page transition might be a case of premature optimization. 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 using web server access logs works pretty much the same as it does for a traditional MPA using SSR.

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 use the History API throughout your application, none of the work you did in step 2 will be wasted! Your application needs the capability to reconstruct client-side state following a page reload in any case, to support those user actions that require it (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 by primary-clicking links or traversing the browser history, 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 such a thing is 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 reason for this. In the next post I’ll address a vexing and potentially contentious question concerning terminology.


  1. I chose 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