This is a transcription of Rich Harris' talk at Jamstack Conf 2021. I've also added links to related content or topics as appropriate. You can watch the recording on YouTube, and I highly recommend you do so—Rich is an engaging speaker, and there's some visual elements I couldn't capture in the text.
I made this transcription to make the content easier to reference by myself and others. If Rich Harris or the conference organizers would like me to take this down for any reason, please DM me on Twitter.
There's an active debate happening in front-end circles about the right way to build websites, and like most front-end debates, both sides are really attacking a caricature of the other. On the one hand, we've got advocates for what is often referred to as "modern web development." On the other hand, we have people who look at the state of modern mode development and argue that it's time for a bit of a "come to Jesus" moment about the path that we're on.
For brevity I'm going to call these camps the modernists and the traditionalists, but I don't want you to read any judgment into those terms. My goal in this talk is to try and tease out some of the claims and counter-claims and present what I think is going to become an increasingly popular approach to web development over the next few years. The debate is often reduced to MPA, or multi-page app, versus SPA, or single-page app. That framing really doesn't do either side justice, but nevertheless it's a good place to start.
A multi-page app is really just a website. When you go to that site, after all the DNS stuff happens, you connect to a server or a CDN in front of the origin server and it sends you some HTML. That HTML might have been dynamically generated for that specific request, it might be a cached version of a response that was generated earlier that day, or it might come from a static file server that was updated the last time the app was. Somewhere in that HTML perhaps there's a link. If you click it, the same thing happens again. The browser connects to a server, gets some more HTML, and when it gets enough of a response the existing page is removed and the browser starts rendering the new page.
Normally on the web, if you middle click or command click a link it will open in a new tab. On this food delivery website that's ignored. It will navigate in the current page instead. If you then click the back button the layout will jump around for a bit before a nausea-inducing scroll back to where you were. There's lots of these little accessibility details that SPAs often get wrong—focus management, scroll management, navigation announcements, page titles, command click behavior—that collectively make the entire web a less predictable and less accessible medium. We shouldn't accept that. I pick real world examples, albeit more or less at random, because we need to reckon with the fact that single-page apps have kind of ruined the web.
The problems SPAs solve permalink
So the backlash to modern web development is understandable, but it's important to remember that SPAs do in fact solve some real problems with the traditional approach. They also give you new capabilities. [Showing music library app] Here's something you can't do in a traditional app—you can't navigate from one page to another while continuing to play media. In an SPA, that's extremely straightforward.
Here's another: you can't use client-side state management that persists across navigations. [Showing email app] In this app, the first load contains a subset of my data. If I scroll, I load more. If I then click into one of these items then click back, I should be at the same place in the list, even though a fresh page load would exclude everything except the first tranche of results. This sort of thing is a little tricky to pull off in a single-page app, but it's essentially impossible in a multi-page app.
[Showing calendar app] Or consider transitions. Native app designers understand the importance of motion and object constancy in user interfaces, but on the web we tend to teleport instantly from one place to another—not because it's better, but because that's all browsers are capable of. In a single-page app, we can change that. I should note that there's a proposal in the works to add navigation transitions to the platform and it often gets brought up in these conversations, but look. I'm glad that it's happening, but don't imagine for a moment that it'll be as powerful as single-page app transitions can be.
Comparing MPAs and SPAs permalink
So let's look at some of the pros and cons of these two approaches side-by-side. In particular, let's look at the MPA advantages.
[The following was not spoken, but appeared on a slide]
- Server-rendered (or static file, etc) - fast initial load
- Consistent experience with accessibility features built in
- Use whatever technology you like
- Single codebase
- Fast navigation
- Persistent elements
- Client-side state management
- Two apps instead of one
- Navigation can be sluggish
- JS (including shitty third party JS) must be evaluated on every page load
- Lack of resilience
- Typically poor initial page load performance
[Okay, back to the talk]
You probably already know that most modern frameworks support server-side rendering. If you're building everything by hand, then you might have a bit of a hard time setting everything up, but if you're using a so-called "meta-framework" like Next or Nuxt or SvelteKit, then you get that behavior out of the box, so you don't need to sacrifice that fast initial load.
What about accessibility? If you're manually implementing navigation logic and so on then you'll probably end up making mistakes here, but again, modern meta-frameworks take this stuff pretty seriously.
What's better than a spa? permalink
So we can build apps that combine the best aspects of traditionalism and modernism: a fast initial load, accessibility, resilience, instant navigation, a cohesive code base, and capabilities that used to be out of reach. What should we call them? Well, we already have a sea of acronyms that we use to describe all these various techniques, so at the risk of being all xkcd 927, maybe there's a new acronym that we could invent. What's better than a spa?
HTML Optimized Through Techniques Users Believe in.
Super Awesome Usable Neato Apps.
Better Applications Through HTML Hyper-Optimized Using Scripts... Etc.
Okay, these are all terrible. I'm sorry: I really thought that inspiration would strike and I would be able to come up with something in time for the conference, but that didn't happen, so I started googling to see if there's a word for the synthesis of traditionalism and modernism, and it turns out that the interior design community has thought about this. They call it "transitional design."
I'm going to read a paragraph from ApartmentTherapy.com:
Whereas traditional design can sometimes feel prim and stuffy, and modern design can lean too heavily on the sleek and streamlined look, transitional design samples elements from each aesthetic to form an equally classic and fresh feel. Think of transitional design as having the best of both worlds.
They could be talking about web development. I actually really love this, and not just because I'm a sucker for interior design porn. There's an obvious linguistic connection to the kinds of transitions I was talking about earlier. For too long, we've modeled web apps as discrete pages that you jump to, rather than cohesive spaces that you move around. And it's not because one mode is universally more appropriate than the other; it's because our thinking has been constrained by the medium. I often recommend this website, HUDS+GUIS, where motion designers for TV and film imagine what user interfaces could look like if we were freed of our technological constraints. I want a web with more design freedom, and transitions are a big part of that.
But leaving all that aside, this word "transitional" resonates with me. It's a humble word that recognizes that we're in a constant state of evolution. It doesn't pretend to have all the answers, but it promises that we're going to keep seeking them. It looks towards the future, but it's respectful of the past. In short, it's everything we should aspire to be.
So, Jamstack Conf, let's coin a new term: #transitionalapps. Let's see if we can get this hashtag trending. In fact, you know what? I think I just figured out the title for this talk.
On HTML Over The Wire and GitHub permalink
I want to talk a little bit about what transitional apps look like in practice, particularly as it relates to SvelteKit, which is a meta-framework we're currently building. But first I need to talk to the eye rollers in the audience, because I guarantee there's a few of you.
Look, I think this is a really cool idea, but I'm not totally convinced it works in practice. It turns out it's really hard to have things like optimistic updates when the rendering logic lives on the server, so the responsiveness of your app is effectively dictated by network latency. I don't want to be too critical of this idea because it's a good fit for a certain class of application, but I do think we need to be honest about its limitations.
It's not Hotwire but a Rails app that often gets mentioned in these conversations and uses a similar technique is GitHub. I love GitHub. I rely on GitHub, and back in the day it was one of the first big applications that used the History API to do client-side navigation and it was a real wow moment, but the front end is super buggy.
Let's say you go to your issues list and click on one of the unreads. You decide you don't want to deal with it right now, so you back out. Hang on a minute, it's still got the blue unread marker. Refresh the page and it's fixed, fine. Actually let's close that issue. Hang on, we still have an open issue—or do we?
It turns out that when you send partial HTML updates instead of having the rendering logic and the state live in the same place, you get inconsistencies everywhere. I've seen PRs that have both the green open lozenge and the purple merged lozenge on the same page. It's incredibly disorienting to the extent that I obsessively refresh every GitHub page after navigation almost as a nervous tic.
Another example: if you try and interact with the page while actions are running, there's a good chance that it will go haywire. Why? Because we're sending partial HTML updates instead of data. This kind of fragility is essentially baked into this development model. You can fix it, but you'll always be fighting an uphill battle.
Documents versus apps permalink
Another common objection is that documents and apps are fundamentally different and it's senseless to use app development tools to build document sites. I definitely agree with the sentiment that you should use the right tool for the job, but I want to question the underlying premise here. Look at this product page from an e-commerce site: is it a document or an app? Clearly it's a little bit of both. It's mostly text and images, but it also has buttons that do stuff like "add to cart." My day job involves building interactive widgets that live on New York Times article pages. News sites are classic examples of the document-based web, but this pretty clearly has app-like characteristics.
We talk about documents versus apps as though there is a dichotomy, but it's not: it's a spectrum. When we erase the stuff in the middle we do the web a great disservice. It's a medium that by its very nature resists definitional boundaries.
Now of course you can point to the extremes on the spectrum and say they don't count. But there's no reason your personal blog shouldn't have instant navigation, for example, and if you wanted to add a video player containing your conference talks that people could watch as they continue to navigate around your site, then you shouldn't have to throw away your old stack and begin afresh with a new foundation.
We've already got things like Cloudflare Workers, Netlifly (sic) edge handlers... Netlifly. Netlify.
We've already got things like Cloudflare Workers, Netlify Edge Handlers, Deno Deploy, and we're going to see more entrants in this space. What these platforms let you do is run code cheaply, close to where the user is, with none of the cold start headaches that you associate with lambda, and none of the maintenance or scaling concerns that come with running servers.
But there is some real truth here. If you have a server-rendered page and you're hydrating it with an interactive client-side app then you will end up serving data and component code that isn't strictly necessary. This is probably the least compelling part of the modern web story right now, but it's a very active area of research and development.
The React team is working on server components, which is sort of like HTML over the wire except vastly more sophisticated. Marko is doing something called partial hydration, which means skipping the code and data for non-interactive subtrees. Qwik is aggressively lazy-loading everything, so you don't load code until you need it for a specific interaction. Astro is tackling this problem with so-called "islands architecture", which is less granular than Marko but gets you most of the way there. Svelte, the framework I help maintain, uses a compiler to make the cost of the framework as low as possible, and though we don't yet do any kind of partial hydration you can easily turn off hydration at the page level.
And I guess that's as good a segue as any into a little demo of what we on the Svelte team have been working on recently. SvelteKit, our meta-framework, is essentially a toolkit for building transitional apps (hashtag). If you want to follow along at home, please do.
First we do
npm init svelte@next. Once we hit a stable release soon, it'll just be
npm init svelte. Follow the prompts and your project gets scaffolded to the current directory or whichever directory you specify. Install dependencies, then run
npm run dev -- --open to start the development server and open the app in a new tab.
This is a simple demo app. This page has a little dash of client-side interactivity, just so you can check that the server-rendered HTML hydrated as expected. If we follow this instruction and edit
src/routes/index.svelte, we can see what happens if we edit the source code: it updates the page immediately using Vite's hot module reloading. In fact, if we turn on VS Code auto saving and turn the delay right down, we can see that hot module reloading keeps up with our keystrokes, though I don't necessarily recommend it.
If you're editing styles, then component state will even be preserved, which makes it super easy to tweak the design of your site. If we do
npm run build followed by
So the next time you hear people saying that multi-page apps are best or single-page apps are best remember: the truth is way more nuanced and it's way more exciting. Thank you for watching.
Want to find out when I post a new article? Follow me on Mastodon
or subscribe to my RSS feed
. I also have an email newsletter
that I'll send out when I post something new, along with anything else I find interesting.