In the early days of the Internet, websites were typically statically rendered entirely on the server side, which generated HTML content and sent it to the client's browser. User interactions such as animations, using external APIs, event handling, navigation, etc., were possible, but the user had no means of permanently influencing the content received. Upon page reload, it reverted to its original form.
Another approach was client-side rendering (CSR). After initially fetching HTML, CSS, and JS files, the browser used JavaScript to render the page's content. CSR enables the creation of highly interactive web applications, where most functionalities operate dynamically in the client's browser. Users can perform various actions such as clicking, dragging, or inputting data, and the application responds instantly without page reloading. Additionally, there's a clear separation between the presentation layer and the data/business logic. The frontend, or presentation layer, can be developed independently of the backend, facilitating scalability and application management.
The use of CSR resolved the user interaction problem, but it introduced two issues associated with client-side rendering. The first is page loading time. The browser must first download the entire JavaScript code and execute it before the user sees the page's content. The second issue is SEO (search engine optimization). Search engines prefer server-side rendered content because it's easier to index than content generated dynamically by JavaScript in CSR.
However, these problems have also been addressed. Server-side rendering (SSR) serves as the solution. With SSR, the page is sent as ready-to-display HTML to the client's browser. As a result, the user receives the content faster because they don't have to wait for JavaScript code to download and execute on the client side. SSR also ensures better indexability of content. Content generated on the server side is immediately available in HTML form, making it easier for search engine robots to index and understand the page's content. SSR pages have a greater chance of achieving better search engine ranking.
What is hydration and what to use it for?
The newest approach in this lineup is hydration. It enhances user experience by delivering a JavaScript-light version of the page. Consequently, the page loads faster. Subsequently, after fetching and adding JavaScript code to the page, it enables interaction and often allows for loading only the changing fragments, without the need to download a new version of the entire page. As a result, we reduce the browser and server load, translating into quicker application performance and improved user experiences. Additionally, hydration maintains the consistency of the application state between server-side rendering and client-side, which is crucial for applications requiring user session persistence and application state retention.
A closer look at SSR and hydration
Server-side rendering is a process where web content is generated on the server and sent to the client as fully rendered HTML. This means that users receive pre-rendered content when they initially access a website, resulting in faster load times and improved SEO performance. SSR is particularly beneficial for applications with a significant amount of static content or dynamic data that can be pre-rendered on the server. By serving pre-rendered HTML to users, SSR ensures that content is immediately visible and accessible to search engine crawlers, boosting overall website discoverability and user engagement.
On the other hand, hydration involves re-rendering client-side components after receiving and processing pre-rendered HTML content from the server. This technique enables websites to achieve faster subsequent page loads, interactive user experiences, and seamless content updates without requiring full-page reloads. Hydration is well suited for single-page applications that rely heavily on client-side interactions, real-time data updates, and dynamic content loading. By combining server-side rendering with client-side hydration, developers can strike a balance between initial page load performance and interactive user experience, creating a seamless browsing experience for visitors.
This diagram shows the differences between server-side rendering, simple hydration, and resumable hydration. Key to understanding the effects of these different approaches are the timing of First Contentful Paint (FCP), Largest Contentful Paint (LCP), and Time to Interactive (TTI).
A glance at Qwik and Next.js
The essence of Qwik applications lies in their remarkable resumable capability, allowing seamless transitions between server-side and client-side rendering states. This unique feature empowers Qwik to initiate rendering on the server, delivering a fully rendered HTML response to clients while enabling smooth continuation on the client side for interactive experiences without full page reloads. This fluid transition is owed to Qwik's architectural prowess, ensuring the preservation of the application state across rendering environments. As a result, users enjoy consistent experiences irrespective of where rendering begins, alongside expedited interactions and navigations within the application.
Qwik
A key concept of Qwik applications is their ability to be resumable from a server-side-rendered state. This means that Qwik applications can start rendering on the server side, providing a fully rendered HTML response to the client. However, they can also be seamlessly resumed on the client side, allowing for interactive and dynamic behavior without the need for full page reloads.
This resumable nature of Qwik applications is made possible by its architecture, which allows for the preservation of the application state between server-side and client-side rendering. This ensures a smooth and consistent user experience, regardless of whether the initial rendering occurred on the server or the client. Additionally, it enables faster subsequent interactions and navigations within the application, as only the necessary data and components need to be rehydrated on the client side.
/** @jsxImportSource react */
import { qwikify$ } from '@builder.io/qwik-react';
import { useState } from 'react';
// Create React component standard way
function Counter() {
// Print to console to show when the component is rendered.
console.log('React <counter/> Render');
const [count, setCount] = useState(0);
return (
<button className="react" onClick={() => setCount(count + 1)}>
Count: {count}
</button>
);
}
// Specify eagerness to hydrate component on hover event.
export const QCounter = qwikify$(Counter, { eagerness: 'hover' });
Qwik has several higher-order functions that are essential for its proper functioning. One of them is qwikify$, where the value eagerness indicates the moment at which Qwik undergoes hydration. Qwik's functions can be easily identified by the fact that they end with a $ sign. Apart from these, the components are fully reactive components (React is used, not emulated), which facilitates easier transition from other libraries using React.
Next.js
Hydration in Next.js demonstrates exceptional effectiveness and versatility, making it highly effective compared to hydration in other libraries. An important aspect is that hydration is an integral part of Next.js, seamlessly cooperating with its architecture and features. This ensures smooth and reliable operation, eliminating the need for additional tools or libraries. Additionally, Next.js offers built-in performance optimization mechanisms, such as pre-rendering and lazy loading, which effectively minimize page load times and accelerate the hydration process. This is crucial, as speed and fluidity of interactions are key factors in providing a positive user experience.
Another significant aspect is Next.js' support for various hydration scenarios, including server-side rendering (SSR), static site generation (SSG), and client-side rendering (CSR). Thanks to this versatility, developers have full control over the hydration process and can tailor it to the specific needs of their applications. Furthermore, Next.js' integration with various tools and platforms facilitates the management of the hydration process and performance monitoring of applications. This enables easy customization and optimization of hydration, resulting in higher performance and responsiveness of applications.
import Image from 'next/image'
import profilePic from './me.png'
export default function Page() {
return (
<Image
src={profilePic}
alt="Picture of the author"
// width={500} automatically provided
// height={500} automatically provided
// blurDataURL="data:..." automatically provided
// placeholder="blur" // Optional blur-up while loading
/>
)
}
In Next.js, we won't notice any additional wrapping functions around components. Instead, we can use components provided by Next.js that have already been optimized. Like, for example, the Image component, which automatically detects the size of the image and reserves space for it on the page to avoid layout shifts after loading.
In summary, hydration in Next.js stands out not only for its exceptional efficiency and versatility but also for its integrity in the context of the framework's architecture and features. With built-in performance optimization mechanisms and support for various scenarios, Next.js is an excellent tool for building modern and efficient web applications that meet the highest user standards.
Real-life comparison
The graph shows a comparison of the performance of a page with the same content, depending on the tools it was built with. The numbers are in milliseconds and were obtained using Lighthouse (less is better).
Conclusion
In summary, hydration and server-side rendering (SSR) stand as pivotal techniques in the realm of frontend development, each offering distinct strategies for striking a balance between performance and interactivity in web applications. These approaches play a crucial role in enhancing user experience, particularly in scenarios where rapid content delivery and dynamic user interactions are paramount.
Hydration, as exemplified in frameworks like Next.js, serves as a means to seamlessly augment server-rendered content with client-side interactivity. By injecting JavaScript functionality into pre-rendered HTML, hydration enables the application to become fully interactive upon initial load, without sacrificing the benefits of server-side rendering. This approach ensures a swift and responsive user experience, promoting engagement and satisfaction.
On the other hand, SSR excels in scenarios where search engine optimization (SEO) and initial page load times are critical considerations. By generating fully-rendered HTML on the server and delivering it to the client, SSR ensures that content is readily accessible to search engine crawlers and users alike. While SSR may entail longer initial load times compared to hydration, it lays a solid foundation for subsequent client-side interactions, contributing to a seamless and efficient user experience.
It's imperative for developers to carefully evaluate the strengths and limitations of each approach and select the one that aligns most closely with the requirements of their project. While hydration and SSR offer valuable tools for improving user experience, they are not universally applicable solutions. Rather, they should be judiciously employed based on the unique demands and objectives of each application. By leveraging these techniques thoughtfully, developers can create web experiences that are both performant and engaging, ultimately driving user satisfaction and achieving project success.