The Sisyphean Quest for Web Performance

ayoub_alouane

Ayoub Alouane

Posted on August 29, 2023

The Sisyphean Quest for Web Performance

It's been more than 12 years since we started using SPAs (Single Page Applications). This approach was revolutionary for DX (Developer Experience). However, in terms of web performance, we experienced a decline unlike any seen before, leading to catastrophic times for interactivity. Even now, we see this issue with applications built using frameworks that solely rely on SPAs. These frameworks have tried to improve their rendering methods. In this article, we will explain the evolution of rendering on the web, culminating with a spotlight on a new rendering technique that many believe will revolutionize web performance.

The history of web development often begins with a total SSR, PHP & JQuery, for example. During those times, DX (Developer Experience) was challenging, and riddled with errors, and the code was complex and difficult to debug. While this article won't delve extensively into those aspects, it's worth noting that these tools offered developers complete autonomy over their application's architecture. Thus, any performance issues were often the result of mistakes in their code. In contrast, with SPAs (Single Page Applications), it's usually the framework itself that becomes the root cause of major performance problems.

Client Side Rendering (CSR)

Single Page Applications (SPA) marked a significant shift in web development, a true revolution in building web apps. Initially, we were thrilled with the outstanding UX it provided. However, as time progressed, we began to encounter performance bottlenecks because we have started to use SPAs for startup critical applications such as ecomerce, where startup matters a lot . Our apps took longer to become visible and interactive. Often, users were greeted with a blank screen at the start, forced to wait. The more our apps relied on JavaScript, the longer these delays became. Regrettably, as we scaled our apps, we often sacrificed performance. This increasing complexity largely stemmed from one root issue: JavaScript's limitations.

The question here is, it's because of what? it's because we should wait for the browser to download, execute, and render all the javascript of our application, to be visible and interactive, this figure shows us how it works:

Client Side RenderingImage Source: https://www.youtube.com/watch?v=k-A2VfuUROg&feature=youtu.be

To address these performance challenges, we experimented with various methods like lazy loading, preloading, and code splitting. However, as history would have it, these enhancements proved insufficient.

Server Side Rendering (SSR)

As previously mentioned, SSR is a traditional method of rendering. However, it became essential to revisit this approach to tackle certain challenges. The primary concern was performance, prompting a reimagined approach to how SSR operates.

After addressing the SPA issue, frameworks such as Angular and React sought to enhance their method of delivering page content to the browser. Instead of sending a blank HTML and then downloading the JavaScript for the entire web app to execute, they choose to generate the webpage on the server. This server-rendered page contains HTML, CSS, and also data from a database or an API. Once this is sent to the client, we have a page that is visible, completely visible, but unfortunately not interactive, so the JavaScript should be delivered and executed on the client side in order to add handlers to the DOM nodes, it meas that our page will be visible twice. This process is known as hydration, re-executing of application code in order to make the static page interactive. In simpler terms, the DOM will be "hydrated" with JavaScript.

Server Side RenderingImage Source: https://developers.google.com/web/updates/2019/02/rendering-on-the-web

the Time to Interactive is comparable to that of an SPA, if not longer, due to larger HTML payload. This marks the beginning of an extensive journey during which all frameworks will strive to optimize the hydration process as much as possible.

Static Site Generation (SSG)

For websites focused on content that requires minimal interaction and feature content that doesn't frequently change, SSR might be an overkill. It can hinder the Time to First Byte, and CSR might negatively impact our Core Web Vitals. This calls for an alternative method, introducing Static Site Generation (SSG). With SSG, an HTML page is produced during build time for each potential route. So, if there are 3,000 articles, an individual HTML page will be generated for each during the build phase.

Static Site GenerationImage Source: https://www.patterns.dev/posts/incremental-static-rendering

This approach appears to be efficient with great performance, but it's clear it has a drawback. For instance, every time a new article is created in the database, the entire website must be rebuilt and redeployed for that article to become accessible to users, here comes another approach.

Incremental Static Site Generation (iSSG)

The name is clear, and the goal is clearer, iSSG is to fix the problem that we had with SSG, we don't need to rebuild and redeploy our websites, with iSSG we will be able to add new pages and also update existing pages.

It's important to note that SSG or iSSG is best suited for sites that require minimal interactions and don't frequently update page content. While these rendering methods excel in terms of SEO and performance, they aren't ideal for web applications that demand extensive interactions with APIs or frequent changes to the page's state.

While these methods aim for peak performance, they don't fit every scenario. Hence, a more versatile approach that addresses a broader range of cases is necessary.

Progressive Hydration

The concept of progressive hydration in SSR is straightforward: enhancing the hydration process. With traditional Server-Side Rendering, while the page is visible, it isn't interactive – meaning buttons won't function initially. This is because we must wait for the entire JavaScript code to be downloaded and executed on the client side. However, progressive hydration, as its name implies, allows for the step-by-step hydration of our page. This means the hydration occurs node by node within the DOM. As a result, some buttons may become interactive immediately, while others lag behind due to the ongoing hydration process. Once this process concludes, the entire page achieves interactivity.

Static Site GenerationImage Source: https://www.patterns.dev/posts/progressive-hydration

This method is good, but it still uses hydration, just in a better way. The challenge is guessing which part of the page a user might interact with next. It's great that users can quickly use parts of the page, but they might have to wait if they want to use other parts. They won't get full interaction until the whole page is ready.

Streaming Server-Side Rendering

We've just delved into progressive hydration, where the DOM is hydrated step by step. Now, picture a scenario where the entire rendering process for the web page, including HTML and CSS, is also done progressively.

Static Site GenerationImage Source: https://www.patterns.dev/posts/streaming-ssr

With this method, we send chunks of HTML and JavaScript to paint the user interface as quickly as possible. This ensures that users can interact with parts of the page almost immediately. This technique is effective in minimizing the Time to First Byte (TTFB), which is beneficial.

Selective Hydration

Every server-side rendering technique we've discussed so far has its merits, but they aren't one-size-fits-all solutions. Consider a page we want to render using SSR that has a component requiring data on its initial load from an external API. This means there will be a delay in rendering the HTML on the server, which isn't ideal.

Enter Selective Hydration. Instead of halting the entire page's rendering while waiting for a data-intensive component, this approach sends other components to the client first. These components are rendered and made interactive while the browser awaits the server-fetched data for the specific component in question.

Selective HydrationImage Source: https://github.com/reactwg/react-18/discussions/37

Clearly, the hydration process for other components continues uninterrupted. This method boosts performance without negatively affecting other parts of our website.

React Server Components

In the React world, this marked a revolutionary shift. RSC introduced a new approach to building web apps, transforming how components are managed and rendered more efficiently and effectively.

RSC is more server-centric to enhance the client-side rendering, delivering only what's necessary. For exemple, if a component should only be visible under a certain condition, instead of making that decision on the client-side, it can be determined server-side. This avoids sending the component to the client, benefiting web performance since there's no need to download and execute a component that will never be displayed.

RSC has a zero-bundle size, allowing us to render static content that might have previously required JavaScript libraries for construction. In the past, this was done client-side, using libraries for content that didn't need interactivity. With RSC, we can handle this server-side, producing only static content with no additional bundle size.
For more details: https://github.com/reactjs/rfcs/blob/main/text/0188-server-components.md#zero-bundle-size-components

With RSC, we gain secure, full access to the backend, as server components are never delivered to the client. This approach allows us to fetch data directly from the database, eliminating the need for an endpoint.
For more details: https://github.com/reactjs/rfcs/blob/main/text/0188-server-components.md#full-access-to-the-backend

RSC enhances both user experience (UX) and developer experience (DX). In RSC, all imports automatically become code-splitting points, eliminating the need for additional configuration. This not only facilitates the developer's work but also boosts performance, as the server determines whether or not a component should be rendered.
For more details: https://github.com/reactjs/rfcs/blob/main/text/0188-server-components.md#automatic-code-splitting

To deep dive in RSC you can consider reading this RFC: https://github.com/reactjs/rfcs/blob/main/text/0188-server-components.md

Islands Architecture (Partial Hydration)

Now we are in a new era, an era of 0 JavaScript, the Island Architecture, It's a new way of building websites without having the need to hydrate all our website in the first load, this concept of Islands Architecture was popularized first by Astro Framework using a technique called Partial Hydration.

The concept of partial hydration allows developers to determine what aspects are loaded and when in order to provide fast interactivity. This is done by being able to put some client-side JS only where it is needed. Partial hydration definitely is a wise and practical strategy. Our webpage is initially sent as pure HTML from the server to the client. JavaScript is not shipped by default.

Islands Architecture (Partial Hydration)

In the example of Astro, the guiding principle is to build Islands of Interactivity that the browser can hydrate on its own. We can optimize how the page loads javascript by treating each component (Island) independently.

Resumability

In our earlier discussions, we delved into how various frameworks aimed to optimize the hydration process. Hydration, traditionally seen as a bottleneck for website performance, has often been the focal point for improvement. Yet, instead of merely enhancing what's existing, it might be time for a revolutionary shift in our approach. The question isn't just about doing it faster but possibly about doing nothing at all.

Introducing "Resumability." The name itself gives a clue: to "stop and resume." This concept suggests picking up right where the server left off, eliminating the repetitive nature of traditional hydration processes. Resumability's primary goal is optimal performance, emphasizing minimalistic javascript and pure HTML.

Consider a contact form on the page's bottom. Traditional methods preload the necessary JavaScript, while partial hydration slightly improves this. But with Resumability, the JavaScript loads only upon direct interaction, such as clicking the send button. This innovative perspective, championed by the emerging framework Qwik, offers a fresh take on web app development, focusing on scalability without sacrificing performance.

Resumability

If we try to think deeply about the figure above, we will understand the reel problem, scaling. If we have an extensive web application, it will take a lot of time to load with hydration.

How it is done in Qwik? in Qwik there is code extraction for optimizing performance. For each event on our page, a specific JavaScript file is linked. If an action isn't taken, like pressing a button, its JavaScript isn't loaded. Qwik doesn't only load files upon interaction.

After the initial interactive page load, it preemptively downloads the necessary javascript code for the current page via a service worker, storing it in the browser's cache. This prevents constant server requests. Instead, it pulls from the cache, or if not yet available, fetches it from the server—this is called prefetching. Unlike other frameworks that bog down initial loads with JavaScript, Qwik remains interactive without waiting for the JavaScript to be downloaded.

Using this approach, Core Web Vitals like FCP, LCP, and TTI experience significant enhancements directly through the framework, without requiring developer interventions or specific optimization techniques. In contrast, many other frameworks necessitate manual optimizations, from code splitting and lazy loading to caching and more.

Qwik takes away this pressure from developers. Instead of getting bogged down with configuration, they can focus solely on app development. The framework takes care of the rest, ensuring optimized performance. This innovative approach not only enhances user experience (UX) by improving performance but also streamlines the developer experience (DX).

For more details, you can read this article:

Conclusion

In web development, the quest for the best web performance possible has been nothing short of Sisyphean. The evolutionary journey through CSR, SSR, SSG, and others showcased the industry's relentless pursuit of excellence. However, each approach had its limitations, and none seemed capable of fully overcoming the performance bottlenecks. Then emerged a glimmer of hope "Resumability.", offering a promising future where performance no longer feels like an uphill battle.

Article resources:

-https://www.patterns.dev/
-https://github.com/reactjs/rfcs/blob/main/text/0188-server-components.md
-https://dev.to/this-is-learning/qwik-the-post-modern-framework-3c5o
-https://dev.to/this-is-learning/astro-framework-169m
-https://developers.google.com/web/updates/2019/02/rendering-on-the-web
-https://web.dev/vitals/

💖 💪 🙅 🚩
ayoub_alouane
Ayoub Alouane

Posted on August 29, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related

The Sisyphean Quest for Web Performance
javascript The Sisyphean Quest for Web Performance

August 29, 2023