There’s a feeling in the air. A zeitgeist. SPAs are no longer the cool kids they once were 10 years ago.
Hip new frameworks like Astro, Qwik, and Elder.js are touting their MPA capabilities with “0kB JavaScript by default.” Blog posts are making the rounds listing all the challenges with SPAs: history, focus management, scroll restoration, Cmd/Ctrl-click, memory leaks, etc. Gleeful potshots are being taken against SPAs.
I think what’s less discussed, though, is how the context has changed in recent years to give MPAs more of an upper hand against SPAs. In particular:
- Chrome implemented paint holding – no more “flash of white” when navigating between MPA pages. (Safari already did this.)
- Chrome implemented back-forward caching – now all major browsers have this optimization, which makes navigating back and forth in an MPA almost instant.
- Service Workers – once experimental, now effectively 100% available for those of us targeting modern browsers – allow for offline navigation without needing to implement a client-side router (and all the complexity therein).
- Shared Element Transitions, if accepted and implemented across browsers, would also give us a way to animate between MPA navigations – something previously only possible (although difficult) with SPAs.
This is not to say that SPAs don’t have their place. Rich Harris has a great talk on “transitional apps,” which outlines some reasons you may still want to go with an SPA. For instance, you might want an omnipresent element that survives page navigations, such as an audio/video player or a chat widget. Or you may have an infinite-loading list that, on pressing the back button, returns to the previous position in the list.
Even teams that are not explicitly using these features may still choose to go with an SPA, just because of the “unknown” factor. “What if we want to implement navigation animations some day?” “What if we want to add an omnipresent video player?” “What if there’s some customization we want that’s not supported by existing browser APIs?” Choosing an MPA is a big architectural decision that may effectively cut off the future possibility of taking control of the page in cases where the browser APIs are not quite up to snuff. At the end of the day, an SPA gives you full control, and many teams are hesitant to give that up.
That said, we’ve seen a similar scenario play out before. For a long time, jQuery provided APIs that the browser didn’t, and teams that wanted to sleep soundly at night chose jQuery. Eventually browsers caught up, giving us APIs like querySelector
and fetch
, and jQuery started to seem like unnecessary baggage.
I suspect a similar story may play out with SPAs. To illustrate, let’s consider Rich’s examples of things you’d “need” an SPA for:
- Omnipresent chat widget: use Shared Element Transitions to keep the widget painted during MPA navigations.
- Infinite list that restores scroll position on back button: use
content-visibility
and maybe store the state in the Service Worker if necessary. - Omnipresent audio/video player that keeps playing during navigations: not possible today in an MPA, but who knows? Maybe the Picture-in-Picture API will support this someday.
To be clear, though, I don’t think SPAs are going to go away entirely. I’m not sure how you could reasonably implement something like Photoshop or Figma as an MPA. But if new browser APIs and features keep landing that slowly chip away at SPAs’ advantages, then more and more teams in the future will probably choose to build MPAs.
Personally I think it’s exciting that we have so many options available to us (and they’re all so much better than they were 10 years ago!). I hope folks keep an open mind, and keep pushing both SPAs and MPAs (and “transitional apps,” or whatever we’re going to call the next thing) to be better in the future.
Follow-up: More thoughts on SPAs
Posted by Carson Gross on May 21, 2022 at 11:50 AM
I propose the term Hypermedia Driven Applications (HDAs) for a new generation of web apps built using both the old, web 1.0 approach, and not-really-new-but-resurging approach of using javascript to augment browser hypermedia capabilities:
https://htmx.org/essays/hypermedia-driven-applications/
libs like turbo.dev, htmx and unpoly all fall into this category and attempt to enhance the native hypermedia abilities of browsers rather than replace the hypermedia model with what amounts to a thick-client-talking-to-a-data-api approach.
Posted by Nolan Lawson on May 21, 2022 at 12:38 PM
Yeah, I think these hybrid approaches are super interesting. The lines will probably get increasingly blurred in the future.
For me, the bright dividing line between MPAs and SPAs is whether it has a client-side router. I believe Turbolinks is an SPA by this definition, but HTMX is not. Although more and more frameworks might just make it optional, which would blur the lines further.
Posted by Geert on May 21, 2022 at 7:11 PM
Would be nice to at least once expand the TLA (three letter acronym) into its full version. That way readers unfamiliar with the TLA can follow the article.
Posted by Nolan Lawson on May 22, 2022 at 6:45 AM
Sorry about that. I used
<abbr>
tags, but of course they don’t work in all environments.Posted by Evan Byrne on May 22, 2022 at 10:40 AM
I’ve been a fan on MPAs far longer than I have been building SPAs, so it’s great to see things improving there! That said, there seem to be some implicit assumptions in the anti-SPA movement. One is that there are large and widespread performance issues with modern SPAs. These are greatly exaggerated. Next.js seems to run perfectly fine on mobile phones for instance. The other assumption is that SPAs increase complexity. In many cases this is true! However, with current frameworks one might actually find it easier to bootstrap with a SPA framework so they can use React in someplace than patch together state management, HTML injection, and event handling for non-trivial interactions in a MPA.
Posted by Nolan Lawson on May 22, 2022 at 5:20 PM
That’s fair, yeah. An SPA can be performant – I’ve built one called Pinafore, which scores very well on Core Web Vitals. I know from personal experience how hard it can be, though, to get all the fiddly bits right – focus, scroll, etc. Thankfully most SPA frameworks try to do this for you, but in my experience there’s typically some hand-holding required (e.g. async updates that need to run right before scroll restoration).
You’re right that keeping all the state in one place (the main thread) is conceptually much easier than having it spread out across the server and client. I think this will be one of the trump cards for SPAs moving forward. I guess the point of my article, though, is that if the only reason you’re using an SPA is because “it makes navigations faster,” I don’t think that’s actually true anymore in most cases. It wasn’t ever true for pages with lots of HTML that can be streamed, as Jake Archibald outlines here. Maybe it’s true when the part of the DOM that’s being updated is very small, or if there’s a lot of JS being loaded on every navigation, but I don’t know what the threshold is.
There’s also the possibility of using the current SPA mindset, but moving rendering logic to the Service Worker and building an offline MPA that way. Here is an example I found. You’d still have state split between the main thread and the Service Worker, but at least you don’t have the network boundary between the two. Anyway, food for thought!
Posted by Kim Johannesen on May 25, 2022 at 2:00 AM
The Chrome team are also working on bringing page transitions to the web: https://www.youtube.com/watch?v=JCJUPJ_zDQ4
If the other browser makers are implementing this as well, that’s one more argument for SPA’s that is obsolete.
Posted by Nolan Lawson on May 25, 2022 at 2:32 PM
Yep! This is the “shared element transitions” I mention in the post. It’s had a few names over the years. :)
Posted by andreaslagerkvist2016 on May 26, 2022 at 9:00 PM
Would love to see it come to MPAs especially if it can be handled with nothing but CSS. Is that in the plans do you know?
At the moment an SPA seems to a requirement? I actually built a demo just recently and kept thinking how nice it’d be if it worked with MPAs: https://sleekwp.dev/fun/page-transition/
Posted by Nolan Lawson on May 26, 2022 at 9:31 PM
Yeah I think it’s planned for MPAs but currently designed for SPAs. Some details here.
Posted by Itamar on May 25, 2022 at 2:43 PM
I think ultimately the answer will be somewhere between the two. Many projects will be MPAs, but the few projects (like Figma) that aren’t feasible as an MPA will be SPAs. The web dev space these days has too much of a black and white mentality – either SPAs are the answer to everything and MPAs are horrible, or MPAs are the answer and SPAs are horrible. There should be space for both approaches.
Posted by Jon on May 27, 2022 at 4:18 PM
I’ve been fascinated and impressed by the approach Remix is taking. It’s MPA progressively enhanced to SPA, picking up the benefits of both approaches in a smart way. I think we’ll see a lot more in this space in the next few years, especially with services for compute and data on the edge becoming more available.
Posted by Boris Egorov 🇺🇦🤍💙🤍 (@boris_egorov_) on May 29, 2022 at 6:52 AM
Can we just call them “websites” without introducing new abbreviation? :-)
Posted by Rob Schlüter on May 30, 2022 at 5:01 AM
Yes! I fully agree.
Posted by 前端周刊第二十一期 | 呱唧呱唧网 on May 29, 2022 at 6:27 PM
[…] The Balance has Shifted away from SPAs – 这不是最简单的事情,尽管至少有一个(复杂的)书面规范用于JSON.stringify's 的操作。 […]
Posted by Best Of tech #44 - Atol Open Blog on May 31, 2022 at 6:33 AM
[…] développeur Nolan Lawson a posté trois articles sur la pertinence du choix du modèle SPA (Single Page Application) lorsqu’on met […]
Posted by SPAs, Shared Element Transitions, and Re-Evaluating Technology | CSS-Tricks - CSS-Tricks on June 1, 2022 at 8:31 AM
[…] Nolan Lawson sparked some discussion when he described a noticeable shift away from single-page applications (SPAs): […]
Posted by Возрождение простых сайтов. Статика, 0kB JS, ничего лишнего / Habr on June 13, 2022 at 5:39 AM
[…] Тенденция видна везде. Современные фреймворки даже хвалятся «0кБ JavaScript» по дефолту, а браузеры внедрили […]
Posted by Bob on July 1, 2022 at 9:49 AM
I have had very good results using “reveal.js” which is much like PowerPoint ™ for the web. In this case, the web pages are actual interactive presentations.
Is there a “system” or “framework” that would demonstrate this capability as a MPA rather as SPA?
Posted by Hacker Bits, Issue 80 - Hacker Bits on August 28, 2022 at 7:28 PM
[…] The balance has shifted away from SPAs by Nolan Lawson […]
Posted by Hacker Bits, Issue 84 - Hacker Bits on December 26, 2022 at 9:36 AM
[…] The balance has shifted away from SPAs by Nolan Lawson […]
Posted by Ti Point Tork » Blog Archive » Nat’s 2022 Technical Link Pile: Dev, Architecture, APIs on December 30, 2022 at 10:14 PM
[…] The Balance has Shifted Away from SPAs – for simple things. Some scenarios still need […]
Posted by mb21 on May 25, 2023 at 11:37 PM
That actually works with the bfcache, as that restores complete DOM and JS state!
Posted by Humbird0 on December 17, 2023 at 9:31 PM
A persistent audio player could be implemented using a good old-fashioned Frameset. They’re ancient, but they still work in current browsers. It’s basically like having 2 iFrames with the audio player in one, and the rest of the webpage in the other. When you navigate pages in a frame, it does not affect the other frame.