DEV Community

Huy
Huy

Posted on • Edited on

MPA, SPA and Partial Hydration

If you've been on Twitter lately you might've seen the term "Partial Hydration" as well as "MPA" going along in long threads of discussions by web developers, advocators and framework creators, wondering what about them?

MPA

"Multi-page-app", yes, the name is mainly to oppose "Single-page-app". The idea is now we're back to the web era 10 years ago - truly "static" websites, each link navigation triggers a fully page reload to the new html page, no SPA-style navigation (where navigation doesn't trigger a full page reload but replace the current page with the new page, app-like experience).

Why?

The web community have spent huge effort to make SPA faster and more SEO friendly than how it was first introduced: SSR and streaming to make meaningful content visible as fast as possible. But then we've come to realize a fact: nothing beats MPA in term of page load speed and SEO capability.

SSR did a wonderful job bringing faster page load to SPA, but the problem persist: we've been sending way too much, mostly unused JavaScript to users.

Why not SPA?

With the current advanced web technologies, mainly web libraries/frameworks like React, Preact, Vue, most companies opt to build their web products fully in these technologies, and since these frameworks are excellent in building SPAs, we're having a lot more SaaS products as SPA now more than ever.

SPA isn't a bad thing, it makes navigations feels "native" and smooth with preloads and background fetching new pages which user will likely visit. In fact it's so great that a lot of big corp products was build based on SPA: facebook, twitter, notion, .etc.

However, SPA does come with a cost that is not so obvious: initial bundle size.

The SPA story

The way most SPAs works is that (almost) everything in our web UI is built with JavaScript (with or without frameworks), those JavaScript then run and create html in run-time, the most basic form of this pattern is document.createElement("div"). Now, imagine every texts, buttons, sections, popups, every UI pieces of our website are built using this pattern: why is it not-so-good that we're introducing MPA again?

The answer: It's the cost of JavaScript. Unlike raw HTML, using JavaScript to create HTML implies invisible overheads: it is the cost of downloading, parsing, and then executing JavaScript. So before we can see anything on the screen, the browser has to go all that way in order to show us something meaningful. Depends on contexts, this effect can cause a long delay between when a user visited a site and when a meaningful content is visible to that user. Web performance tools nowadays measure this delay as one of the most important metrics (LCP).

SSR & hydration

Then we have SSR "Server Side Rendering", to improve LCP and provide better SEO capability for SPA, in React this is ReactDOMSever.renderToString. The process is simple: render the whole React tree to raw HTML on server and returns it to browser, thus user can see content immediately after HTML is downloaded.

Now we already have our perfect world with SPA & SSR: app-like experience with fast content time!

No, not yet, we're still missing one final piece - the raw HTML returned by our server will not become fully interative (.eg "counter" state won't change when clicking buttons). In order to make our page interactive, we have to "hydrate" it.

Hydrate is the process of turning the raw HTML we returned earlier to fully interactive React tree of components. For React this can be done using ReactDOM.hydrate method. So full process would be:

  1. renderToString & send the (html) string to clients on the server
  2. Loads the corresponding React component and hydrate it on the client:
    • import Page from "pages/home"
    • hydrate(Page, document.getElementById("app"))

"Sooo that's still all good!". Yes, except one thing: do we really need ALL components to be interactive? Probally not, especially when we're building a static landing/marketing site using React, where most of our content is static except subscribe forms, sliders .etc. Hydrating the whole page in this scenario is called hydration waste: only some components are actual interactive components, yet we hydrate everything from top down which in turn make time-to-interactive (TTI) longer.

Partial Hydration

This concept proposes hydrating only parts of the whole site - parts where we needs interactivity, this in turn help us ship less JavaScript to the client by only hydrate demanded components, thus improving page load time & time to interactive. As of now only some of static sites frameworks support this out of the box (which I know of): Astro & Marko.

To implement this by hand in a React project, although doable, requires quite a bunch of works and can be hard to debug issues in the process.

MPA, SPA or Partial Hydration?

Same old answer: it depends, if you're building an analytics dashboard, sport betting portal or a better version of PowperPoint, stick with SPA (and SSR, if needed), because these types has lots of interactive UI to make Partial Hydration just isn't worth it.

Otherwise, if it's your landing site similar to netlify.com or dwarves.foundation which has only 1-2 pieces of UI that require JavaScript to run, and you're building it with React: try to apply Partial Hydration where possible. While react-static or gatsby is not (yet) support partial hydration, you can try out astro.build, it has first class support for one and is a decent static site generator with React support.

For MPA, if you prefer to keep building your site with React, you can still make it an MPA: just do SSR on server and NOT hydrate the whole page on client. Obviously interactive components won't be interactive, but that's a start.

Conclusion

Finally, all of this perf, SEO, page load time et all are not the metrics to measure a product's success - which is in a whole different universe. If you have a slow web product, benchmark it, fix it and repeats, it's not the end of world.

You might have a fast website, but they have 1 million users.

Top comments (0)