Isomorphic sites, Hydration and the Prism compiler

Ben
4 min readOct 13, 2020

There is a growing trend in web development to server side render sites built with client side frameworks. This addresses the issue that client side frameworks are slow at initial render due to the need to wait for JS to download, parse and run. This is particularly a issue for contentful sites. A 10 second startup for figma is okay but for reddit or news sites there is more demand for content not interactivity. So server side rendering fixes that problem by serving content before runtime has fully booted up.

With that comes the complication that client side is now not the start of the world and that a server render step has come before. The biggest issue from this is booting up state on the frontend

During SSR of client side sites, the server often sends down large JS (or JSON) blobs that the client framework uses as its runtime state. For example Ctrl-U on www.nytimes.com and go to line 1890 sees window.__preloadedData initialised with a large JS object. This JS object accounts for 2/3 of the payload.

Most of this state is used to generate the first vdom frame that runtime will used to compare / diff after any render calls. Apart from that most of the object is not needed by runtime and the whole thing is sent over the wire and loaded in memory during initial page load.

Techniques such as partial hydration attempt to reduce this object but I thought I could do one better.

So I built my own framework / compiler named Prism to solve this.

One aim of Prism was to implement a effective handover of server state to the client runtime so it would run as if the data was initialised / created on the client. And as a compiler and using its existing reactivity system it was quite simple to do.

The following is a output from prism-ssr-demo, a example of using Prism compiler with express to build full stack web apps.

The demo has no injected objects, makes no subsequent requests and can perfectly recreate server state (when asked)

Kind of magic ;). and it pulls this with room to spare. The client side is bundle is 9.67kb (2.96kb Gzip) and that includes more such as normal reactive bindings:

There are several features of Prism hydration which gives it a leg up against standard hydration:

  • Values are pulled from the DOM rather than having to send a object (with the same information) down
  • Values are only pulled out of DOM lazily. Only when the value is referenced is it pulled out of the DOM and added to the state.
  • Values pulled have correct types (notice upvotes from JSON.stringify is of type number)
  • Values are on a per property basis. Not really shown in these example but when upvotes was incremented, only this first post object was created and from that only the upvotes was hydrated in (not title or id).

DOM level hydration is not a new thing but I haven’t seen any tooling for automatically doing this. There isn’t a term for this technique but it would be something like JIT markup based partial hydration

and this is from quite a simple and intuitive templating syntax similar to single file components from Vue and Svelte:

As a compiler this is all automated. All that needs to be fed in is the templates and will generated code for rendering on the client and server as well as hooks for binding state to views.

There are a few concerns with this technique:

First is the overhead in scripting. Although the state blob is now gone there is additional JS that has to be loaded to do the hydration. These functions are fairly small with a single statement. They would be more expensive if the state has a lot of properties with short strings or numbers. But for regular hydration payload scales with the size of its JSON.stringifyied state whereas Prism hydration scales with the amount of keys the state object has. In 99% of situations a Prism site will have a smaller total payload because of large strings and arrays of complex objects. The other win is that state objects will change across requests whereas the hydration logic is constant as thus can be cached between requests.

The second is that the state may be modified to what is the view. Take this modification to the post component:

The server view will now show twice the number of upvotes for each post component. Prism compiler is starting to work on expression reversal and will correctly retrieve the correct state even if the markup is not 1:1 with what state is. For more on this see the GH Issue.

There is a lot more to say about Prism and how it fairs with a production next.js app 👀. but for now you can:

You can view the demo repo, view the compiler repo, spin the demo up in replit and follow development @kaleidawave

Code snippets rendered with carbon

--

--