Back to Blog
ProductDock: Nemanja Vasić

8 minutes read

Exploring the Rendering Landscape: Client-Side vs Server-Side vs Server Components

Nemanja Vasić

Software Developer

Nemanja Vasić, our software engineer, explains various rendering strategies in the frontend world in his blog post, “Exploring the Rendering Landscape: Client-Side vs Server-Side vs Server Components,” focusing on the React and Next.js ecosystem.

Nemanja highlighted the differences between client-side and server-side rendering, underlining their respective advantages and limitations. Additionally, he discussed Server Components, a new approach to rendering that Next.js introduced.

History from the 90s to the present


Have you ever wondered how people made web apps in the 90s and 2000s?

In the 1990s, PHP, ASP, and JSP were popular scripting languages used for developing web applications. They all relied on SSR (Server-Side Rendering), meaning the server-generated HTML and sent it to the client (browser). Every user interaction required the server to generate a new HTML page and send it back to the client, resulting in a complete page reload for each interaction.

With the advent of AJAX (Asynchronous JavaScript and XML) in the 2000s, web applications began to employ dynamic rendering. AJAX allowed web pages to update asynchronously by exchanging small amounts of data with the server behind the scenes. This update allowed developers to update parts of a web page without reloading the entire page, resulting in a smoother and more responsive user experience.

Then, the community introduced JavaScript frameworks such as Angular and Backbone.js, along with libraries such as React.js. Alongside these advancements, they introduced the concept called CSR (Client-Side Rendering) which quickly became a thing and everyone was using it, especially combining it with REST for data exchange.

Looking ahead to the present, we have embraced hybrid approaches that combine SSR and CSR to create more optimized, SEO-friendly applications.

Additionally, there are Static and Dynamic Rendering, which are not part of this blog post but are worth mentioning. Next, we come to the newly introduced Server-Side Components by the React and Next.js teams, which raised a lot of questions in the frontend community. So, without further ado, let’s explore these rendering strategies.

CSR (Client-side rendering)

Let’s start with CSR. I will use React.js to showcase examples.

ProductDock: CSR
CSR – Initial index.html that browser receives from the server

The above image shows what the browser receives as the initial HTML. Anyone who has worked with React remembers that “root” is usually found in index.js or App.js. However, bundle.js matters here. It contains all the Javascript code needed to mount and run the application, React, and other third-party dependencies. But how does it actually work?

The server-hosting React application sends this initial (empty) HTML to the client (browser). Then, the browser downloads bundle.js. When it finishes downloading, React begins its execution by creating all DOM elements, connecting them to event listeners, handlers, and other parts needed to make the application interactive, and inserting all of that into the “root” element.

Afterward, the browser takes charge of all user interactions through Javascript. This approach allows web pages to load faster and respond more smoothly to user interactions by fetching and rendering only the necessary data without requiring full page reloads, thereby enhancing user experiences. Additionally, it enables the creation of highly interactive and dynamic web applications that can provide real-time updates and seamless navigation.

ProductDock: Visual representation of CSR
Visual representation of CSR

The above image shows a visual representation of the timeline. I have added one extra box (run DB query) to add a bit of complexity—we will come to that part later in the text.

Downsides of client-side rendering

As long as your application remains small and with browsers becoming increasingly powerful, everything runs smoothly. Despite the user starting with a blank page, the Time to Interactive (TTI) and the entire process – from downloading the bundle to React springing into action – happen swiftly.

However, as the application grows, bundle.js becomes more significant and heavier so that the blank page the user initially gets is present for longer and longer. In this case, we usually start to include loaders so that the user knows what is happening. CSR often requires fetching data asynchronously from the server after the initial page load.

This process can introduce additional latency and complexity in handling API calls, managing loading states, and handling errors. Another downside of CSR is that search engines may have difficulty crawling and indexing content that is dynamically rendered with JavaScript.

SSR (Server-side rendering)

ProductDock: Server-side rendered
Server-side rendered index.html that the browser receives from the server

To explain SSR concepts, we will use Next.js to showcase examples. SSR solves some of the above-mentioned problems. Instead of sending an empty HTML file, the server will render our application and send the created HTML file to the client. As you can see, bundle.js is still there because we still need React, which will make our application interactive.

React now doesn’t have to create DOM elements from scratch, unlike CSR. Instead, it accepts already created DOM elements sent by the server, passes through them to create a virtual sketch for the UI (known as the Virtual DOM), and connects the elements with event handlers where necessary. This is called hydration.

[To find out more about hydration, click here].

ProductDock: Visual representation of SSR
   Visual representation of SSR

Let’s explain the above image.

The user comes to the application. The user sends the request to the server. The server generates the initial HTML and sends it to the client. Next, the client (browser) downloads bundle.js. React does its job: It hydrates DOM elements, and the user sees the content on the page. After that, we send another request to the server, “run the DB query,” after which React rerenders the page, and the user finally sees the final content.

The above-mentioned process is one way to implement SSR, but it is not the only one. Another popular way to implement SSR is Static Site Generation.


In the application build, you need to compile the React application, converting JSX into plain old JavaScript and bundling all modules into that bundle.js.

What if we also included rendering applications during this process? This approach is called Static Site Generation (SSG), a web development technique in which HTML pages are pre-rendered at build time, creating static files that are served to users, ensuring fast load times and better performance.

Now, the difference is clear. With SSR, the user is faced with a more seamless experience. Instead of the blank page, we get with the CSR, we now have static content, such as the header, sidebar, footer…, or non-interactive content – a significant improvement indeed.

Let’s now consider another example. In the above case, we present the user with initial content and then send another request to the server to get new data.

Is there a way to avoid that second request to the server? There is, but it would mean that we need to somehow get this new data before we render the initial content.

What we can do is to move “run the DB query” to execute on the first request to the server. Now if you have experience with React you would know that we would need to somehow tell React “Hey this piece of code needs to execute exclusively on the server”.

ProductDock: Example of reducing the number of request to the server
An example of reducing the number of requests to the server

So far, this has not been possible if we used just React unless you use some of the existing frameworks.

Javascript frameworks
Javascript frameworks

All of these Javascript frameworks have their own solution for executing part of the code exclusively on the server. We will take Next.js as an example. When a request reaches the server, the server first executes the getServerSideProps() function, handles the DB query or REST call as needed, creates a props object, and returns it to the component that renders the data.

After that, the component that is rendered on the server is sent to the client to be hydrated there (in other words, to be rendered on the client as well). The server exclusively executes the getServerSideProps() function. So, this part of the code will never reach the client. It will not be in that bundle.js at all.

However, it’s important to be aware of the limitations of Next.js. You can only execute the getServerSideProps() function when you export it from a component that is a page component, specifically a component defined in the pages folder. This is the case if you use [pages router].

Organizations can embrace agile practices without restructuring by assigning employees from relevant business units as Product Owners. This approach empowers individuals to make informed product decisions, fostering collaboration and preventing decision-making bottlenecks.

Starting with a Product Owner role is a practical step before establishing a formal product management function long-term. Moreover, it reinforces the importance of empowering and respecting the individual responsible for the product’s direction.

Executing a piece of code
An example of executing a “piece” of code exclusively on the server-side

Downsides of server-side rendering

While SSR offers benefits like improved initial load times and better SEO, it comes with trade-offs in server load, development complexity, performance with dynamic content, and SEO for client-side interactivity. Choosing between SSR and client-side rendering depends on the application’s specific needs and constraints.

RSC (React server components)

Now, let’s talk about React Server Components.

At the time of writing, RSCs are still an “experimental” feature of React, and they are available in React v19.0.0. As you probably guessed, this will offer a way to tell React to “execute this piece of code exclusively on the server side.”

What is important to emphasize about RSCs is that it is a new paradigm. Most of us who have worked or are working on React applications are used to seeing components as client-side components. Things are completely different here because RSCs are components that are exclusively rendered on the server.

This unique approach ensures that RSCs are never re-rendered, making their output immutable.

Another important note is that a lot of React’s API is  not compatible with RSCs. For example, we can’t use hooks, useState() can’t be used because state can change, and RSCs can’t re-render. And we can’t use effects because effects in React run after initial render on the client side, but RSCs are not rendered on the client, remember they only render on the server.

Now, you might ask yourself how I could then use client components. You need to use the “use client” directive.  See the example below. More details on [directives]. 

Using client component
 Example of using client component

RSCs are the new kid on the block.  “Standard” React client components are an old kid on the block, now called “Client Components.” RSCs run exclusively on the server. Code that we write in RSCs will not be included in the bundle.js; they never re-render or hydrate. If we want to add interactivity to Server Components, we could compose them with Client Components by using the directive “use client.”

Nemanja Vasić

Nemanja Vasić

Software Developer

Nemanja is a seasoned Software Developer with over five years of professional experience in the information technology industry. His current tech stack comprises ReactJs, NextJs, Javascript, NodeJs, Java, Spring Boot, and MongoDB, among others.
He holds a degree from the Faculty of Technical Sciences, University of Novi Sad, and has a proven track record of delivering high-quality software solutions.


Related posts.