The case of partial hydration (with Next and Preact)

At Spring, we are responsible for maintaining various different news media websites for our parent company, Axel Springer. One of our latest releases, welt.de, is the fastest news media website in Germany; one of our most sought after goals is to continually achieve the best performance possible and the reason for this is simple: better performance usually means a better user experience and thus a higher user-retention.


tl;dr

Scroll down to “Recap” for a short summary with an infographic. The key points for this in a nutshell:
  • Performance is crucial for the web
  • to achieve high performance we want to send as little as possible to the client
  • we can do this by choosing the components we want to send and hydrate to the client
  • we leave the rest of the page static and have multiple render roots
  • All of this works with a single code base
  • Below is a lengthy article on how we implemented the steps noted above. You will also find a link to a WIP repo of this implementation here:
  • https://github.com/spring-media/next-super-performance

Performance on the web

If you follow Addy Osmani you already know the drill, he writes a lot about the cause and effects of performance on the web. To get you started, I can recommend Addy’s article on “The Cost Of JavaScript In 2018”. Two very important things you can learn from this article are:

  • The cost of JavaScript is not only the time it takes to load your bundle
  • The time to parse and execute your JavaScript is just as crucial

Of course there is a lot more to performance than this, including loading strategies, a critical rendering path, performance budgets and so forth. All of these things revolve around how to optimize whatever you end up sending to your client. What we want to focus on with partial hydration is not how to optimize what you send, but how much you send at all.

A key aspect of this will be server side rendering (SSR) because there is a lot we can do on the server that does not need to be done on the client. In fact, this is the crux of this article; Whatever can be done on the server should be done on the server but the client should only be sent whatever needs to be executed on the client side. Additionally, you can still apply everything you know about web performance but you’ll have far fewer factors to manage, this will be explained in depth further on down the article.

SSR and hydration

In order carry out our objective of building a performant website we will be using a modified version of Next. Next comes bundled with plenty of built in performance enhancing features, most importantly, Next does server side rendering (SSR) out of the box. This means that Next will take your app, written in React and in this order:

  1. Render it as an HTML string on the server
  2. Send the rendered HTML string to your users as source code
  3. Send your React code as JavaScript to your users
  4. And then finally “hydrate” your HTML using your React code

To “hydrate” in this case means that Next will roll out your React code over your HTML and then tell React something a little like this:

Hey React, here’s some HTML that matches exactly what you would render if I told you to render in an empty DOM node, please do not re-render everything, instead, please just use the HTML as if you had rendered it and carry on with your day

React will answer

Ok, I just looked at you HTML and it seems to match exactly what I would have rendered. It’s cool. I’ll just attach some event handlers to your DOM and your page now acts as a single page application like I did it all myself in the first place.

The benefits of loading a website in this way are simple: Your users will already see a fully rendered page when they load your website (instead of a blank page) and then it becomes interactive. Your website will also benefit from a major performance improvement because the browser does not need to do any repaints (re-rendering your page with React).

Too much overhead

This approach is fantastic when you want to create web applications or in other words websites that need to be fully controlled by JavaScript and are also interactive wherever you click. Examples of this approach in production include websites like Facebook, Twitter and web-based email clients.

But most websites aren’t like this, most websites are kind-of-static and also contain some interactive elements.

Now you end up sending your entire application code to your users including React components for every headline or text paragraph anywhere on your page. The result is an unnecessarily huge bundle that needs to be loaded, parsed and executed. This results in suboptimal performance, your page will be slow(er) especially for mobile users and for no good reason!

And that sucks.


So what can we do? Well, there are lots of strategies and products out there. One of the most prominent ones is Gatsby, a static site generator (I’m sure you’ve heard of it by now) which focuses heavily on performance optimization. The issue with Gatsby is that it has to generate all of your pages and subpages at compile time which does not really work when you have sites linked to a CMS that updates everyday and that hosts millions of articles—which is exactly what we need for our news media sites. This is why we are using Next as well as modifying it to suit our needs.

Enter partial hydration

To solve the above mentioned problems we came up with something we like to call partial hydration.

If you have looked into this topic you probably will have come across terms like progressive hydration or lazy hydration. They don’t all mean the same thing specifically but they are all interrelated and belong in the same sphere.

The basic idea behind our version of partial hydration is: Instead of doing SSR and then sending your entire application to your client, only parts of your application’s JavaScript would be sent to the client to hydrate the parts of your website which specifically require JavaScript to work. If you were to create a website using such a method you would have multiple tiny React ‘apps’ with multiple render roots on your otherwise static website.

Doing things in this way should give your website a massive performance boost because what you end up shipping is plain HTML, CSS and the least amount of JavaScript required for your page. One thing to note, when measuring performance you must not only factor in loading time but also parsing and execution time as well.

You can also choose to implement proper performance strategies on top of all of this, like code-splitting, caring for the TCP slow start and your critical rendering path etc.

Our implementation

Our implementation consists of 2 packages:

  • The Preact library for partial hydration called pool-attendant-preact
  • The Next.js plugin called next-super-performance

The latter library is just a plugin for Next.js that uses pool-attendant-preact so let’s concentrate on pool-attendant-preact. I may write a followup post on next-super-performance at some stage in the future.

pool-attendant-preact

Layout with a header, body, sidebar and 2 reactive elements

Picture this layout and let’s pretend the grey boxes are elements that can be completely static and you want the ones in purple to be interactive. For instance, the larger one could be a Twitter feed and the smaller one a tiny voting tool. So we need to apply JavaScript to these elements to make them interactive and we want to leave the rest as static elements.

An example implementation for this using pool-attendant-preact could look like this:

page.jsx using 2 hydrated components and HydrationData

Lines 3–8 are all the components we want to display, these components will be rendered on the server and then sent as HTML and CSS to the client without any JavaScript whatsoever.

Lines 10 & 11 are where we mark the components TwitterFeed and Poll for hydration and get a new component in return. Lines 18 & 19 are where we use them.

Line 22 is of utmost importance. This is a component that injects hydration data (props and component names) into the page.

But let me explain. When we do a normal hydration with react, your code looks something like this:

SSR hydration with React pseudo code

There are 2 problems we need to solve here when we do partial hydration

  1. ReactDOM.hydrate operates on a root node in the DOM, the node that it uses as a starting point for the hydration. That root node must contain a server-rendered DOM tree that matches your app’s components and state. The catch: You need to explicitly name a DOM node to act as a root node. In this example, this is simple, you can give that node an id, use document.getElementbyId and then throw that node into ReactDOM.hydrate and 🎉 you’re done! 
    Partial hydration on the other hand means that you will have multiple DOM elements on your static page that you need to hydrate. You wouldn’t want to explicitly name them all that would be tedious work for the developer.
  2. What if HydrateTwitterFeed or HydratedPoll need props that need to be passed on to them? Say, something like <HydratedTwitterFeed user="luke_schmuke" />. If we want to run ReactDOM.hydrate(<TwitterFeed user="luke_schmuke" />, rootElementOnThePage) where would we get the luke_schmuke from? How would a static page know about this? We need to somehow store and send them to the client.

Solution

The way we tackled this problem can be understood from the implementation of withHydration:

withHydration.js

Let’s take a closer look at this: withHydration works using the higher order component technique, the higher order component returns the original component along with its original unaltered props but it also prepends it with a <script> tag called hydration-marker. This solves our 1st problem by finding our render roots in our server side rendered page. It works like this, the server will use the HOC (higher order component) to render something like this

Everything in <div class="twitter-feed"> is passed down from the TwitterFeed component. The <script> prepending <div class="twitter-feed"> comes from our HOC. Now that the server rendered bits and pieces have been loaded some client side JavaScript could for instance search for script[type="application/hydration-marker"] and assume that any element directly following the script should be hydrated. It’s also possible to use wrapper elements but that would be too intrusive and it could potentially break your layout.

To address problem #2 let’s take another closer look at the code, lines 5 and 8 of withHydration.js. In line 5 we store the props passed to HydratedTwitterFeed. In our initial example we passed no props at all but if we did something like <HydratedTwitterFeed user="luke_schmuke" /> we would now have the following object stored:

Now our hypothetical client.js could hydrate our component using the DOM element adjacent to our marker using the props that we stored earlier.

But hey, how does the client.js access the data we stored earlier?

This is when <HydrationData /> comes into play. When using this component all the data we have stored using HydrationData.storeProps will be rendered as serialized JSON to the HTML. This process mimics how Next.js renders NEXT_DATA and how Apollo renders APOLLO_DATA.

Highlighted parts of withHydration.js

Also note that calling HydrationData.storeProps will return and ID that we then pass on to the hydration marker, this way we can map the data in <HydrationData /> to its corresponding markers and DOM elements

hydration flow

This is how the hydration part on the client would work:

client side partial hydration
  • First, find all markers on the page
  • Next, find the hydration data and load it
  • And finally, iterate over all the markers and get their subsequent DOM elements and hydrate them using Preact’s render

You may have noticed that in this example we have a component called Teaser which is used to hydrate all of our elements. In our super-intendant-preact module we expose a function called hydrate which enables you to create a component like Teaser to hydrate multiple components.

Recap

There’s a lot of information to take in from this article, my purpose for writing about the subject was to illustrate our particular method of using partial hydration and to explain how it works. If you would like to dive deeper into the code behind this article then please have a look at our GitHub repository:

https://github.com/spring-media/next-super-performance

Here’s a helpful infographic to help you visualize our partial hydration method a little better:

Conclusion

*Our approach to using partial hydration works quite well, we have already used a similar method with Vue which has proven to be successful. I expect to see more written about this topic in the future, some people within the web development community have been working on their own partial hydration methods, the developers behind React have begun to take notice of this so we may see some partial hydration features being incorporated into React’s core in the future.

We like to ship as little code as possible to ensure that we end up with more performant websites which in turn deliver a better user experience, after all, the smaller the codebase the smaller the bundle the user ends up having to deal with. Partial hydration enables us to do this without compromise while using the tooling and eco system of modern web development.