The balance has shifted away from SPAs

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:

  1. Chrome implemented paint holding – no more “flash of white” when navigating between MPA pages. (Safari already did this.)
  2. Chrome implemented back-forward caching – now all major browsers have this optimization, which makes navigating back and forth in an MPA almost instant.
  3. 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).
  4. 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

24 responses to this post.

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

    Reply

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

      Reply

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

    Reply

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

    Reply

    • 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!

      Reply

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

    Reply

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

    Reply

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

    Reply

  7. Can we just call them “websites” without introducing new abbreviation? :-)

    Reply

  8. […] The Balance has Shifted away from SPAs – 这不是最简单的事情,尽管至少有一个(复杂的)书面规范用于JSON.stringify's 的操作。 […]

    Reply

  9. […] développeur Nolan Lawson a posté trois articles sur la pertinence du choix du modèle SPA (Single Page Application) lorsqu’on met […]

    Reply

  10. […] Nolan Lawson sparked some discussion when he described a noticeable shift away from single-page applications (SPAs): […]

    Reply

  11. […] Тенденция видна везде. Современные фреймворки даже хвалятся «0кБ JavaScript» по дефолту, а браузеры внедрили […]

    Reply

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

    Reply

  13. […] The balance has shifted away from SPAs by Nolan Lawson […]

    Reply

  14. […] The balance has shifted away from SPAs by Nolan Lawson […]

    Reply

  15. […] The Balance has Shifted Away from SPAs – for simple things. Some scenarios still need […]

    Reply

  16. Posted by mb21 on May 25, 2023 at 11:37 PM

    Or you may have an infinite-loading list that, on pressing the back button, returns to the previous position in the list.

    That actually works with the bfcache, as that restores complete DOM and JS state!

    Reply

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

    Reply

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.