If you are like me, your web journey began when everything was much simpler. Create an HTML page and some CSS, throw it up on an Apache server, and you were well on your way to a website. Flash forward (pun intended) to what you see on the web today: You must choose between a slew of different web libraries and frameworks that all attempt to solve problems in different ways.
In this post, I dive into some of the various methods JavaScript projects are trying to solve for the age-old problem of performance. Specifically, two metrics seem to surface frequently when evaluating web vitals: Time To Load (TTL) and Time To Interactive (TTI).
- Time to Load (TTL) is the amount of time it takes for a page or application to load and display its content in the users' browser. This means all HTML, CSS, JavaScript, images, and other assets that are required to render the page have been downloaded. The measurement is usually taken from when the page is requested to when the final asset is downloaded, and the page is completely rendered.
- Time to Interactive (TTI) is the time it takes for a web page or application to be "interactive", meaning the user can begin interacting with elements on the page. This is measured from the moment the page is requested to the point in which the page is fully rendered and the JavaScript code has finished executing, allowing for page interactivity.
Background
How a site is rendered--whether at the client or at the server side--directly affects TTL and TTI. For example, with a server rendered page you may see the content sooner but it may take additional time for that page to become interactive.
To better understand why that is, I think it is important to understand the difference between client-rendered and server-rendered applications when it comes to React. When a user makes a request for a page on a client-rendered application, the server returns an HTML document that includes a JavaScript bundle (in the script tags, which also then need to be downloaded), this bundle is then responsible for rendering the UI on the client's machine.
With a server-rendered application, the initial rendering of the UI takes place on the server-side. When a user makes a request for a page, the server generates an HTML document built from React components and returns that document to the user. This page is visible and users can interact with it right away, but until the JavaScript bundle is downloaded, the React components will not be entirely interactive.
Each approach has its pros and cons. While SSR (server-side rendering) can result in better initial load times, SEO benefits, and improved accessibility, it can result in higher server loads and less initial interactivity. CSR (client-side rendering) can result in faster page transitions and interactivity, while sacrificing initial load times, SEO, and accessibility benefits.
Accessibility and rendering techniques
As mentioned above, the rendering technique that is used affects accessibility. Why is that? A few reasons you may see a degraded accessibility experience when using client-side rendering (CSR) include:
- Increased initial load times.
- CSR relies more heavily on JavaScript for rendering and interactivity. So, a user with JavaScript disabled will be more impacted than a website rendered on the server-side.
- Client-side rendered applications often manipulate dynamic elements or interactions with JavaScript on the client-side. Assistive technologies such as screen readers may not be able to properly convey this element or changes. Whereas, many SSR approaches generate HTML with proper markup and accessibility attributes on initial render.
- CSR approaches generally update or change content after the initial page load, which may pose challenges for assistive technologies in detecting or announcing updates.
Hydration
In React, and with many other JavaScript frameworks, a process called hydration takes place. What is hydration? Both client-side rendered and server-side rendered applications produce HTML from React components. This HTML may require event listeners or other behaviors such as animations, interactivity, form validation, etc, to truly be interactive. This is where hydration comes in. Hydration is the process of attaching event listeners or other behaviors to the HTML produced by the client or the server. For example, the image below depicts what a typical server rendered lifecycle may look like:
Image courtesy of Next.js Docs
This process can result in slow initial load times when working with large applications. But there are some options depending on your use case to speed this process up. A common approach is to split the JavaScript bundle into smaller, more manageable chunks and load them asynchronously after the initial HTML has rendered. Another approach would be to use SSR to reduce the number of operations the hydration process requires because the initial HTML page already contains the behavior and event listeners.
Partial or Selective Hydration
You may have also come across the term partial hydration. This is when the hydration process only hydrates small parts of the page that require updating. Astro Islands and React 18's selective hydration come to mind in different implementations of this concept. A nice feature with React's selective hydration is that it prioritizes what it is hydrated based on interactions within the page, such as a button click.
While these two implementations are similar in concept, they are different because React's selective hydration is only available within React itself, whereas Astro's partial hydration is part of the Astro framework which accommodates multiple frameworks.
React's selective hydration would be effective when dealing with large, complex components that may require a lot of client-side processing. Astro's partial hydration would be best suited when dealing with small, modular components that can be loaded on an as-needed basis.
Streaming HTML
A new feature in React 18 is the ability to stream HTML. This allows the rendering of a React component to begin on the server and incrementally send the HTML back to the client as it is generated. This will produce a faster initial load and a more responsive user experience because the user can begin interacting with the application as soon as the initial content is downloaded, while still asynchronously loading additional suspended chunks.
A perfect example of this would be an article that has comments below. You can load the critical piece of the page—the article—without waiting for the comments to load. Using suspense, you could instead display a spinner until the comments are ready. The following image shows this scenario:
Image courtesy of Next.js Docs
While React's streaming of HTML may bring a lot of benefits, it's also important to note some potential downsides:
- Streaming HTML requires a server that supports HTTP/2 or newer and client-side browsers to support it as well. Older browsers or devices may not support this functionality.
- An application using streaming HTML may be more complex than one without and pose a steeper learning curve for a team not familiar with the different techniques being used.
- Streaming HTML can improve the initial load time, but it may require careful consideration of caching and performance strategies. These strategies may include managing server-side caching and rendering strategies to prevent server overload due to frequent streaming requests.
Qwik's Resumable Rendering
Qwik is another front-end framework that aims to improve the performance of applications by providing a unique server-side rendering solution. Qwik calls this unique solution resumable rendering.
The first thing to note is that Qwik is not React, nor does it use React. It does use JSX as its template syntax, which means components may look similar to ones created in React. But, you can also use React within Qwik with Qwik React to make transitions easier if you choose to do so.
Qwik avoids the need to hydrate the HTML returned by the server by serializing all the required information as part of the HTML during the SSR process. It also uses a global event handler that relies on event bubbling to intercept all events. This global event handler means Qwik doesn't need to register handlers on individual DOM elements.
Image courtesy of builder.io docs
With hydration, all event handlers are created and attached before the event is triggered (eagerly). Whereas with Qwik, the creation of the event handler happens after the event is triggered on an as-needed basis (lazily). This is done by deserializing the event handler from the HTML which prevents duplication of the work already done by the server.
Qwik is definitely a tool to watch. With it being a relatively newer platform, it may be lacking from a community standpoint or ecosystem of tooling when compared with other frameworks. And because Qwik serializes its handlers as part of its HTML, it requires those to be eligible for serialization which may prove difficult for certain edge cases.
Conclusion
There are many front-end technologies available today, each with a slightly different approach for improving performance and user experience. When choosing which technique or combination of techniques best fits your needs, the answer unfortunately is "it depends". Each and every use case is different, but choosing a familiar technique whose capabilities and limitations you understand is generally a good solution.
Top comments (0)