In this blog series, I’ll use the term multi-page behaviours to refer to the set of behaviours you expect from a traditional Multi-Page Application (MPA)—even if they’re not implemented like a traditional MPA.
So first, what is an MPA?
A Multi-Page Application (MPA) is traditionally defined as web application that modifies content using full page loads, where the browser sends requests for completely new web pages to the web server, and the web server responds using Server-Side Rendering (SSR) to generate full web pages and return them to the browser for display. Think of a traditional web application that runs on the server and generates entirely new HTML pages when you click a link. The MPA is a well-understood design pattern that is more than 25 years old now.
This traditional notion of an MPA combines two different concerns: it implies a set of behaviours (which affect users) while it also describes a particular implementation (which matters more to developers). But in the modern web, behaviour and implementation have become increasingly separated.
- Behaviours: Does the application have multiple logical “pages”? Is your current location shareable? Does the URL change as you navigate the application? Does the application support deep links and bookmarking? Can you right-click a link and open it in a new browser tab? Can you use the application in multiple tabs? Do the browser’s Back and Forward buttons retrace your navigational steps?
- Implementation: Does the application use Server-Side Rendering (SSR), Client-Side Rendering (CSR), or some kind of hybrid? Does the browser communicate with the server using synchronous HTTP requests or asynchronous API calls? Does it do something funky with WebSockets? Under what circumstances do page reloads occur? Does it use the HTML5 History API?
Until about ten years ago this conflation of behaviour and implementation made sense, since they were tightly coupled. If you wanted to provide all the behaviours expected of an MPA, you had to build a traditional Multi-Page Application running on a server using Server-Side Rendering (SSR). Conversely, if you wanted your application to run in the browser using Client-Side Rendering (CSR), you had to forgo many of those behaviours.
In the last ten years, this has changed. Pretty much any type of web application can support multi-page behaviours—including applications that run entirely the browser, as well as various kinds of hybrid applications. In this blog series, I’ll look at some modern web application design patterns and examine how they support multi-page behaviours.
Let’s set aside the implementational aspects of MPAs, and spell out only the implied behaviours. Here is my initial attempt at doing this, which I might revise in response to new insights or feedback.
|Shareable||You can “share” your current location using the browser’s native Share button, or by copying the current URL from the address bar. This allows you to obtain a link to your current location within the application, for use in emails, documents, messages, and so on. You can also use your browser’s native Bookmarking functionality to store your current location as a bookmark or favourite.1|
|Deep linkable||When previously shared or bookmarked links are opened, they take you back to their original locations. This applies even if the link is opened by a different user, assuming it refers to a resource they have permission to.|
|Refreshable||You can use the browser’s Reload button to refresh the data displayed at your current location. While any unsaved data stored locally may be lost, your location within the application stays the same, and the page is regenerated using the latest externally-sourced data where available.|
|History traversable||You can use the browser’s Back and Forward buttons or select a location from your browser’s history to retrace your navigational steps within the application. This does not mean that Back acts like an “undo” button and reverts changes to state—rather, it takes you back to your previous navigational location within the application.|
|Link operable||Hyperlinks within the application are fully operable. This means they provide a rich set of standard interactions besides primary-clicking, including the ability to right-click and open them in a different browser tab or window, or copy their destination URLs. This applies to both text and image hyperlinks. It does not apply to elements such as buttons that do not act as hyperlinks.|
|Multi-tabbable||You can run the same application in multiple browser tabs or windows at the same time, and switch between them at will—for example to compare different items or records, or to refer to information in one page while completing a task in another page. If the application is authenticated, then signing in to one tab signs you in to all tabs, including those you open after signing in. Using the application in one tab does not terminate your session in the others.|
Later in this blog series, I’ll evaluate various design patterns against these behaviours, so I’ll refer back to this list and use it as a kind of benchmark.
Before we continue, there are some key observations about multi-page behaviours I should make clear.
First class user interactions
When a web application truly supports multi-page behaviours, it treats them as first-class user interactions—not as an afterthought. For example, if a user wants to right-click a link and open it in a new browser tab, that’s up to them. Opening links in new tabs is a normal part of using a web application. An application that properly supports multi-page behaviours will make it happen, smoothly and efficiently.
Granularity and composability
A single user action might exercise multiple of the multi-page behaviours listed above.
Consider right-clicking a link and opening it in a new browser tab, for example. To support this action, an application must be link operable (to allow the user to right-click a link and select “Open Link in New Tab”), deep linkable (to successfully open the target URL’s new page in the new browser tab), and multi-tabbable (if it’s an authenticated application, to ensure the user is signed in to both tabs).
In other words, I’ve defined the multi-page behaviours to be both granular and composable, so they can be combined in various ways to describe common user actions.2
Should every web application support multi-page behaviours?
Not at all! There are many reasons why it might not be desirable or feasible for a particular web application to support multi-page behaviours.
It’s difficult to come up with an exhaustive set of criteria, but a few obvious examples spring to mind. Games, 3D modelling, complex client-side applications that take a long time to “initialize”, and the Google Santa Tracker are all examples of applications that probably don’t need multi-page behaviours, or might not work well with them. In certain types of complex web applications, there are subtleties and edge cases relating to state management which developers need to consider, which can make supporting multi-page behaviours very challenging or infeasible.
It’s worth noting that some web applications support multi-page behaviours at some level of granularity, but not others. For example, specific pages might be accessible and shareable using multi-page behaviours—but once opened those individual pages exhibit single-page behaviour using asynchronous content updates. That is perfectly fine if it makes sense in the context of the application.
The web’s usability deficit
I think it’s fair to say that the web generally suffers from a significant usability deficit with respect to multi-page behaviours. This relates to the set of web applications that ought to support multi-page behaviours from a usability perspective—yet for whatever reason, don’t. And there’s a huge number of them. This includes applications in intranets as well as on the public web.
This usability deficit does not encompass web applications that must avoid multi-page behaviours for some justifiable reason; they’re OK.
Where is content rendered?
When it comes to implementation, web applications are often described based on where they render content. Traditionally, web content can be rendered in three places, summarized below. I’ll use these terms throughout this blog series.
|Static Site Generation
|Content is rendered offline, usually in some kind of build environment, and deposited to a web server or CDN as static content. At runtime, the static content is sent to the browser for display.|
|Content is dynamically rendered at runtime as markup by a web or application server, and then sent to the browser for display.|
|Content is dynamically rendered at runtime by client-side code running in the browser using the Document Object Model (DOM) interface.|
You can think of these as the “holy trinity” of web application design. Web applications can use SSG, SSR and CSR individually or in combination. All three techniques allow multi-page behaviours to be fully supported, even when used individually.
One important thing to note is that when a web site uses pure SSG or pure SSR (or a mixture of the two), then all the multi-page behaviours work by default.3 When a web server sends content in response to a full page load, whether it’s statically or dynamically generated, it automatically produces a separate logical web page, and you don’t need to do anything special to support multi-page behaviours. In fact some types of “special” will actually break them. But that requires additional effort. If you do nothing, multi-page behaviours generally Just Work by default, if you’re using pure SSG and/or SSR.
With pure CSR, that’s not the case. If your web application runs entirely in the browser, then most multi-page behaviours are broken by default. But with a bit of extra work, you can support all of them.
For this reason I’ll focus on pure CSR applications for most of this blog series. But I’ll also touch on hybrid applications that have become popular in the last 2–3 years. These combine SSG, SSR and CSR in new and creative ways.
Of course, many popular web frameworks can be used to implement these foundations—but I won’t talk much about web frameworks. There are several reasons for this. First, web frameworks provide layers of abstraction over what’s happening underneath, and the foundations need to be in place before you deal with abstractions. Second, web frameworks are evolving at tremendous pace, and it’s hard to keep up.
If you want to know how to support multi-page behaviours using your favourite web framework, other people can do a much better job of that than me.
With those preliminaries out of the way, let’s begin!
In the second post I’ll describe a key browser feature that helps CSR applications support multi-page behaviours.
In an earlier version I had a bookmarkable behaviour—but then I decided it’s redundant, because bookmarkable is a special case of shareable: when you bookmark a page, you’re effectively “sharing” it with your future self. ↩
They can also describe missing features in a granular way. For example, some web applications support deep links, but those deep links cannot be generated using the browser’s native Share button. Instead, they must be generated by other means, such as a custom “Permalink” button in the application’s content area. Such applications are deep linkable but not shareable. ↩
The blog you’re reading now is an example of a web site generated entirely using SSG. ↩