Mohammad Anas
Full-Stack Developer
Full-Stack Developer
The way websites deliver content has evolved significantly over the years. In traditional web development, servers rendered full HTML pages for every request. These pages could either be static or dynamically generated based on user-specific data.
With the introduction of Client-Side Rendering (CSR) through frameworks like React, a new paradigm emerged. Instead of sending fully rendered HTML, the server now sends minimal HTML along with JavaScript bundles. The client-side JavaScript takes over, rendering the content directly in the browser.
However, this approach came with challenges, such as slower initial page loads, heavier client-side processing, and issues with SEO. To address these issues, modern frameworks introduced hydration as a solution to combine the fast initial load of Server-Side Rendering (SSR) with the dynamic capabilities of CSR.
While hydration resolves many of CSR's challenges, it introduces new ones, notably the double rendering problem. In this issue, the page is generated first on the server and then again on the client, causing inefficiencies.
With traditional Server-Side Rendering (SSR), the server generates the full page each time a user requests it. These pages could either be static or dynamically generated based on the user's data. Once the page is rendered on the server, the browser receives the ready-to-display HTML, which doesn’t require waiting for heavy JavaScript bundles to load.
This method was the standard way websites were built in the earlier days of web development.
Let’s consider a real-world example. Imagine you have a page similar to Twitter’s homepage, displaying user tweets. When a user makes a request to view the page, the server first determines who the user is. It fetches the relevant data (e.g., tweets) from the database on the server. Once the data is retrieved, the server uses it to generate the HTML page, which is then sent to the client for display.
While SSR provides faster initial loading and great SEO, what happens when a user posts a new tweet?
Before frameworks like React or the AJAX revolution in client-side JavaScript, any user interaction affecting content (like submitting a comment or updating a profile) would trigger a full web request and refresh the entire page, generating new HTML. This could create a heavy load on the server, especially for websites with significant traffic or frequently changing content.
Additionally, back then, templating systems varied significantly between programming languages. Developers would write HTML templates and separately ship JavaScript to update or modify them post-load. This created a chaotic development environment and led to the emergence of a new paradigm: Client-Side Rendering (CSR).
This approach shifts the responsibility of rendering from the server to the browser. Instead of the server sending fully rendered HTML, it sends a minimal HTML structure alongside JavaScript bundles. The browser then fetches additional data as needed and renders the page dynamically.
This enabled fully dynamic and responsive websites, where user updates were reflected immediately without requiring a full page reload. However, CSR comes with several costs:
It’s no coincidence that technologies like GraphQL became popular alongside frameworks like React. React normalized client-side rendering, which required stricter APIs to expose the necessary data for rendering on the client side. However, this also meant users needed powerful devices to handle multiple requests and render content effectively.
Here’s how CSR typically works:
index.html
file, which is almost empty except for references to JavaScript files.main.js
) to render the content.This process can result in significant delays, as nothing is displayed to the user until all these steps are completed. For example, rendering a single component might involve multiple API calls, further increasing the time before the content is visible. While CSR provides dynamic behavior, it comes at the cost of slower initial page loads.
Modern SSR combines the benefits of traditional SSR and CSR. Instead of rendering the entire page on the server, it uses a component-based model where some components are rendered on the server while others are rendered on the client. The server sends an HTML page with static content and placeholders for dynamic content.
By rendering HTML initially, SSR ensures faster load times, allowing users to see content quickly. However, the site isn’t interactive until the client "hydrates" the page—attaching event listeners and enabling JavaScript functionality to make the page dynamic.
While this approach improves user experience and SEO, it introduces the challenge of double rendering:
This duplication of effort can result in inefficiencies.
Let’s examine the SSR process (excluding SSG and ISR):
ABC.com
. Depending on the framework (e.g., Next.js), the server either serves a cached page or generates a new one.While SSR ensures faster initial loads compared to CSR, the hydration process can be delayed by factors like client device performance, CDN proximity, and page complexity.
When transitioning from SPA to SSR, developers trade the fast initial loads of SPA for a more complete user experience with SSR. While SPAs may feel incomplete as data loads, SSR provides fully rendered content upfront, though at the cost of longer first loads.