Front-end architecture is one of the fastest growing areas of software development today. Several innovators are pushing the state of the art to design more powerful ways to create dynamic user interfaces. Much of this work is happening at breakneck speed and in broad daylight.
What is hydration?
Much of the activity around improving the modern front-end architecture focuses on what is known as hydration. To understand what hydration is and why it’s central to modern front-end architecture, let’s look at the high-level concepts at play. To deliver the wonder of responsiveness, every framework must manage the three aspects shown in the diagram below.
The basic message in the diagram is that the frame is responsible for framing the view, maintaining state, and managing the interaction between them. (If you’re familiar with the MVC pattern, you’ll hear it here.)
Once these three pieces are in place, you are good to go. The user can see the page and interact with it.
This approach has the wonderful advantage of simplicity, both for the code at work and for the human minds trying to figure it out. It also has a big drawback: the initial rendering of the page has to wait for everything, and the user has to sit through all that network and browser. Also, unless care is taken, the page will tend to display and then rearrange embarrassingly in the final layout. Not a good look.
This inspired the developers to first try rendering the initial page on the server (server side rendering or SSR) and send it. Then the user has a decent page to watch while the rest of the code and state is sent and bootstrapped. It’s a big simplification but that’s the basic idea.
The time it takes to set up the basic layout is called happy first paint (FCP). The next milestone the page must reach is measured by interactive time (TTI), i.e. the time until the user can actually use the page.
The process of taking the initial page and making it interactive, i.e. hydration.
Server-side rendering limitations
The bottom line is that SSR tends to improve FCP but worsen TTI. So the goal became to strike a balance between the two while maximizing them both, while hopefully maintaining an enjoyable development (DX) experience.
Various approaches have been proposed, adopted, abandoned, modified, and combined in this effort to improve hydration. Once you start looking at the details of the implementation, you’re amazed at how complex it gets. A balanced improvement of FCP and TTI with decent DX? It sounds easy, but it’s not.
One of the reasons for the complexity is that we’re right in the middle of sorting out all the trade-offs; it is a scene that unfolds. Once the way forward crystalizes however, we should expect two outcomes of the emerging client architecture. First, it should create web apps that feel “next-gen”, in the same way that well-built apps today offer a subtle but clearly better experience than a few years ago.
Second, and perhaps more importantly, our improved client architecture should have far-reaching consequences beyond better performance. By addressing and resolving complexity, front-end engineers will arrive at a better model, both for the system and for the mind. A better architecture actually represents a more powerful heuristic. This results in often unpredictable subsequent benefits.
You can see this in action with the responsiveness itself. Responsiveness burst onto the scene because it offered a way to offload the developer’s brain state binding to the framework. But the benefits didn’t stop there. The architecture has become not only simpler, but more coherent. This crisp performance and functionality wins on all counts.
Approaches to improving hydration
The basic trick to improving the hydration situation is to look at things in a more granular way. By breaking view, interactivity and state into smaller chunks, we can load and activate them in stages, optimized for FCP and TTI. Here is a tour of some of the approaches.
The JS-less approach is seen in SvelteKit, for example. This does nothing for pages that require responsive interaction, of course. Frameworks still need to address hydration on pages that act as SPAs.
Astro championed the idea of island architecture. The idea is to determine which parts of the page are static and which parts require responsiveness. With this knowledge, you can fine-tune the page load by skipping the frame content entirely, which never changes, and then loading the other parts (the islands) only when necessary.
It is useful to further this idea to note that it aims to improve the SPA. That is, any static content you identify can just sit there, doing its job without any performance impact. All of your client-side state and navigation is maintained.
On the plus side, this approach allows you to delay loading each island until something makes it necessary (e.g. scrolling in view, mouse click). In practice, however, this often results in charges occurring at a particularly inopportune time (just when the user is doing something).
Lazy loaded limits
Features like React’s Suspense component provide an approach that keeps the basic hydration model in place, but breaks it down along boundaries that are then lazily loaded. This has the advantage of keeping much of the familiar process in place, but the disadvantage of requiring a lot of thought and tweaking from the developer to get good results. Mentally, the developer is able to bridge the world of component layout and code splitting at build time.
Also, lazy loading can’t be very useful, because a lot of the framework still has to be shipped in advance.
Possibility of recovery
Resumability is an idea that was introduced by the Qwik framework. Qwik dives deeper into app elements and creates lazy boundaries between them. (In a way, you might think of it as a highly sophisticated form of lazy loading limits.) Resumability means the client can pick up where the server left off and keep things in fine sync.
React deploys the idea of server components and an associated performance improvement called streaming. Here is a description of how the server components work. Essentially, server components allow you to identify which parts of the application can be run entirely on the server, thus avoiding any client-side rendering penalty.
Streaming is another evolving React technique related to Suspense. The idea here is to allow framing content like HTML to start shipping to the client even before all the required data is ready on the server. This can then be applied when component interaction occurs.
Partial hydration or progressive hydration
Things get a little muddy with these terms. Astro describes its island architecture as partial hydration. This is simply to say that only certain elements on the page are hydrated at a time. This is also sometimes called progressive hydration. These two terms are sometimes applied to other techniques.
We really have three terms here that are tripping over each other: islands, partials, progressives. Regardless, the main idea is the same: we need to break down the app’s structure into smaller pieces in order to make it load smarter.
Let’s try to untangle the terms a bit. Let’s say island architecture refers to bits of Astro-style independent interactivity within a static framework.
Going back, you could say that the idea of breaking down the UI is partial hydration, and Astro’s islands are an example of that. We can’t do it without peril, however, because Astro == island == partial is already floating there. As well, partiel seems to suggest an incomplete state of hydration, which is misleading.
Here again, progressive invites confusion with Progressive Web Apps (PWAs). Perhaps compartmentalized hydration is a good term for the overall idea.
Evolution of the front-end architecture
Among those people are Ryan Carniato (Solid) and Misko Hevery (Qwik). Both are pushing the state of the art, releasing code and information to the rest of the world as they go. Two good places to start with Carnatio work are here and here, and two for Hevery are here and here.
Copyright © 2022 IDG Communications, Inc.