Why I'm not the biggest fan of Single Page Applications

posted on

Sometimes it seems like accessibility experts and other web professionals hate JavaScript. This might be true for some, but most understand that JavaScript can be useful for improving UX and even accessibility. JavaScript solutions are often more accessible than their pure HTML or CSS counterparts.

We know JavaScript is not an enemy, but, admittedly, there is a certain reluctance towards building client-side rendered websites by some developers. These people are often dismissed as the “old guard” because of their seemingly irrational aversion towards modern web development. If you don’t understand where this is coming from, it might seem incomprehensible why someone would not want to build single page applications (SPA), but go for the standard way of building websites, sometimes referred to as multi page applications (MPA), instead.

There has been a lot of criticism about SPAs and especially React recently. On a personal level, I can absolutely understand that. I don’t like Meta and their business model; I don’t like how React thought leaders sold their library as something that it’s not, and I don’t like the bro culture that sparked from this whole thing. On a professional level, I understand that there are people for whom React is nothing more than a tool or a .js file. People who, you know,…just build websites for a living. Although I’m not happy about it, I also understand that JS libraries and frameworks are often the entry point to web development for new web developers. Nevertheless, there is also valid and serious professional criticism. With this post I’m not trying to convince anyone to build their websites differently, I just want to share my view and my experiences as someone who has been building websites for over 20 years, and I try to summarize reasons why some developers, including me, are wary of building SPAs. Before I give you some examples, let me say one important thing: I know that it’s possible to create accessible single page applications and I know that there are people who work with JS frameworks (because they want to or have to), and optimise the shit out of their websites because they care about their users.


A lot of the scepticism comes from the fact that some critical features of a website work fundamentally different in single page applications.

Routing

When you create 3 HTML documents, let’s call them home.html, about.html, and dashboard.html, and you link them, clicking a link the following happens: The browser navigates to the new page, the page loads, the title of the page changes in the browser’s tab, focus is on the body, and, if you’re using a screen reader, the software announces the title of the page. Of course, a lot more happens. This is a simplified and incomplete depiction of the process, but the important bit is that no matter how you’re accessing and activating the link (mouse, screen reader, touch, switch device, etc.), you know what happened, that something happened, and where you are. The loading process and the transition from one page to another tell you that you’ve navigated to a new page. Focus is at the beginning of the DOM, where you would expect it, and your screen reader announces the title of the page automatically.

When I click a link, NVDA, the screen reader I'm using, announces the title of the page, for example “Dashboard - My Website”.

In a Single Page Application, you create only one HTML document and you replace the main content of the page when the user clicks a link. To achieve that, you may have to use a routing library like React Router. Compared to the process of native routing I’ve described earlier, the following happens: DOM content changes, focus is still on the link, the title in the browser tab doesn’t change, and a screen reader announces nothing. If you’re lucky, you know what happened, that something happened, and where you are. If you’re a blind screen reader user, you’ll most likely get no feedback at all.

When I click a link, NVDA announces nothing.

While not intentionally, single page applications, at least in terms of page navigation, are inaccessible by design. The creators of reach router did their best to make routing accessible by managing focus, but since data loading and focus management on route transitions are coupled, it was too complicated to do it correctly. Despite all their efforts, routing became completely inaccessible again, when reach router and react router merged and they stopped doing not-good-enough focus management by default.

Custom routing and focus management done right can actually work even better than the native behaviour without JavaScript, but it must be built manually. You don’t see that in many SPAs unfortunately. I’d rather serve users with the not so great, but consistent default experience than no experience at all.
Anyway, there's a lot we can learn from SPAs and from people with accessbility in mind working with them. There’s fantastic research about that topic by Marcy Sutton (“What we learned from user testing of accessible client-side routing techniques with Fable Tech Labs”).

Note: In the previous demo I was using “Create React App”, one of the recommended toolchains and the basic example in React Router. I can imagine that there are frameworks that support accessible routing by default.

The document title

The <title> of the page is one of the most important elements in a HTML document. Users benefit from a well-formed and descriptive page title in many ways.

To mitigate inaccessible routing and the missing feedback after clicking a link, screen reader users help themselves by pressing shortcuts that announce the title of the page. The page title tells them on which page they are. So even if there’s no feedback, they know they’re on a new page when the title has changed. If the title has changed! In a SPA, you usually have to implement title management manually. Again, this is something that just works in native routing.

Screen reader shortcuts for announcing the page title
Screen Reader Command Announcement
NVDA Ins + T Page title
JAWS Ins + T Page title
Voice Over on macOS VO + Shift + I Page summary, including page title
Voice Over on macOS VO + F2 Page title

There are more reasons you'd want to change the page title. It serves as the label for bookmarked pages/favorites, and search engines use the title in their search results pages. Social media sites, chat or mail applications, and similar software use the title in link previews when no other title is specified.

Before you start sending me links: I know that it's possible, I've done my research. My point is, that it's yet another thing you have to reimplement manually.

DOM changes

DOM changes are not specific to SPAs, but to using JavaScript in general. The thing is that reactive DOM changes are at the very core of single page applications. For every significant change you make, you have to communicate that change not just visually, but also semantically. Live regions, ARIA attributes, or focus management can help with that. You won’t find any information about live regions or using focus() for announcing DOM changes in the official React accessibility docs, the Vue accessibility docs, or the Angular docs.
The more JavaScript we add to our page, the more fragile our interface gets and the more we have to manage and communicate manually, if we want to create equal experiences for all.

JSX

JSX kinda looks like HTML, but it’s not really HTML because it’s a mix of HTML with component tags and made-up attributes (I’m talking about you className). I know that React doesn’t require using JSX, but many new devs learn React and JSX first (and only), because boot camps and indoctrinated communities tell them it's the only real way to build websites and they need to know React in order to get a job. This abstraction can be terrible if you don’t understand the difference between the actual standard (HTML) and the abstraction layer. We’re already really bad at writing native HTML and a language that obfuscates the standard language doesn’t make things better.

<li className="item">
  {isPacked ? (
    <del>
      {name + ' ✔'}
    </del>
  ) : (
    name
  )}
</li>

Before we choose to work with any language that is not native HTML, but compiles to HTML, we should learn HTML first. This doesn’t just apply to JSX, but Markdown, Pug, NunJucks, etc. Our input will be much better if we know how the output should look like.

The page context

In SPAs, you create components. A component might be part of another component, and another component until it finally ends up in the wrapper <div> of your index.html. If you look at the final document of most SPAs, the page doesn’t look like a coherent entity, but more like a dump for components that happen to form a page visually. As already mentioned, HTML is a cornerstone of any accessible website and the page structure (a sound heading outline, landmarks, DOM order, semantic HTML in general) plays an important role.

Don’t get me wrong, I also build components that are part of a system, but I also spend a good amount of time crafting pages that work well. The fact that the HTML document is something that you barely touch, because everything you need in there will be injected via JavaScript, puts the document and the page structure out of focus.
I don’t suggest that a component-based approach is wrong, not even one that is written in a JavaScript environment, quite the opposite, but just because it’s more convenient and efficient to work with a JS framework, doesn’t mean that we have to serve all that JavaScript to the client. The traditional way of serving websites entirely on the client is outdated, but I also don’t believe that concepts like hydration are much better.
It’s worth exploring approaches that separate concerns and try to minimize the amount of JS shipped. Some notable projects are Lit, Astro, Svelte, or WebC.

Performance

Most SPAs probably perform well on my 3,500 Euro laptop and my 500 Euro smartphone, but a Macbook Pro and Huawai P30 are not necessarily the average device the average user uses to access the web. I’m not in the position to talk about the performance implications of client-site rendering and especially React, but there has been a lot of criticism early on because SPAs just don’t perform as well as thought leaders at Meta or Vercel used to make you think.

I suggest you read the following articles:

Complexity

Setting up a SPA from scratch is not simple, it involves many tedious steps. Even if you use one of the boilerplate setups, you’re handling a compound dependency monster that just waits to ruin your day because one tiny detail doesn’t work out of the box the way you want it. There’s so much complexity in these things that it makes some people doubt whether it’s worth the effort and if it’s not maybe smarter to spend the time working on something else, something that actually benefits users.

I guess the biggest criticism here is that it feels like people who believe in the superiority of single page applications and the entire ecosystem focus more on developer experience (DX) than user experience. That sounds like a dangerous blanket statement, but after all these years, I never had the feeling that the argument “better DX leads to better UX” was ever true. It’s nothing more than a justification for the immense complexity and potentially significantly worse UX. And even if the core argument isn't DX, other arguments like scalability, maintainability, competitive ability, easier recruiting (“everyone uses React”), and cost effectiveness, in my experience, only sound good, but rarely hold up to their promises.

Conclusion

I hope this didn’t come off as a rant, it’s not. I’m just trying to explain why SPAs are not the first choice or no choice at all for some people. I’m glad that this diverse landscape of tools exists because we can learn from innovation and use this knowledge to create better experiences. I’ve worked with several JS libraries and frameworks, because it pays the bills, but I’ve never used one for my own projects and I’ve never consulted a client to do so, and I probably never will. The simple reason is that I build websites most of the time, and not complex applications. There is a place and time for JS frameworks and client side rendering, but it’s usually not the right choice for the typical website.