Photo by Martin Abegglen on Flickr
5-part Series: Exploring Web Rendering
- Partial Hydration (a.k.a. “Islands”)
- Progressive Hydration
- Streaming HTML
- Server Components
What an exciting time the past few years have been in the evolution of web rendering technologies! Our team at Babbel is hyper-focussed on performance improvements to our applications this year, and we’re going to be using some very cool new concepts and technologies to achieve our goals. As a Principal Engineer, rather than keep my research and knowledge sharing within our walls, I’ve decided to share what I’m thinking about in hopes to expose and teach these topics to a wider audience, grow a dialog with the wider community, and to reinforce my own knowledge – the best way to learn is to teach after all. Before starting, thank you to Ryan Carniato – creator of SolidJS – whose YouTube streams and dev.to articles have been a huge inspiration and source of guidance for my learning and writing about many technologies and frameworks; I strongly encourage you to follow his work. Without further ado, please join me in my 5-part series exploring some of the more interesting modern web rendering techniques; this article is just the start! If you have any questions or feedback of any kind, please reach out to me on Twitter; I’d love to hear from you!
The influx of new performance-focussed frameworks like Astro, SolidJS, and Qwik has indicated we’re entering a new web era, exemplified by them topping the developer satisfaction charts for rendering and front-end frameworks respectively. In addition, considering the growing correlation between performance, search engine rank, and revenue, understanding the progression of web rendering technologies has never been more important.
Because the application is shared between server and browser, state is as well. If your app has a single source of state as is often the case with Redux, for example, sharing state between server and browser can be as simple as passing your state object through JSON.stringify() then adding it to a <script> element at the end of your HTML output so as to not be render blocking; more elaborate state models require more complex methods of state sharing.
On the server, the DOM is non-existent so its events can effectively be ignored, but on the client they’re treated normally. Using React as an example, when an application is rendered with React.hydrateRoot(), React is smart enough to know that hydration is client-only so event listeners should be bound. However, when rendering that same application with React.renderToString(), binding event listeners is skipped because that function is part of React’s server-only API. As a result, occasionally your code will need to be environment-aware so client- or server-specific actions can be taken when appropriate. Clearly, while your application may be shared between server and browser, isomorphic rendering has additional mental overhead during development that must be factored in when choosing your desired rendering model.
Isomorphic applications can provide multiple benefits over client-rendered ones. Instead of an empty HTML shell like what’s often used to bootstrap a CSR app, a server renders the entire page’s content into HTML which allows browsers to begin parsing the page HTML sooner. Thus, page assets like images, JS, and CSS begin downloading earlier. Similarly, SEO is improved because web crawlers like googlebot can crawl your pages more quickly with the full page’s content available rather than having to wait for all client API requests to respond and the DOM to incrementally update; the latter can be time-consuming and error-prone for crawlers. Furthermore, because the HTML is fully available and assuming the response per page doesn’t change, you can optionally CDN cache or static build your pages for a performance boost, especially the prior which will cause the response to skip talking to your origin server and be returned from as close to the user as possible; latency and server load is minimized as a result. If your application values quick navigation times, the client-side routing of an isomorphic application after initial page load should act similarly to a CSR app with no server rendering.
Now that isomorphic rendering has been explained, how can it be used? The most easy-to-use options are known as “metaframeworks” which take a base rendering framework like React, Qwik, or SolidJS and add additional features like routing and style management resulting in options like Next.js, QwikCity, and SolidStart, respectively, among others. Try them out and see what works best for you. If you have any questions or feedback about this topic or any others, please share your thoughts with me on Twitter. I would certainly enjoy hearing from you! Happy rendering!