Lately I've decided to keep mental notes of everyday concepts I use in my work. Most times I have a good overview of what a concept does but I never really understood what goes on under the hood. I mean, it works innit. But I'm taking a step further with some of those concepts, and today's subject is all about Client Side Rendering(CSR) and Server Side Rendering(SSR).
Before SSR was a thing
We all know how wonderful Single Page Applications(SPAs) are, but to really appreciate SSR, we need to go back a little.
So basically, the traditional way of displaying content on the internet is this little relationship that happens between the client, which is your browser and the server, whatever web hosting platform your content lives on.
The browser asks the server for a page, the server returns an index.html with a <script> tag at the bottom pointing to a main.js file, the browser then makes another request to fetch that main.js and executes it.
<!-- index.html -->
<div id="root"></div>
<script src="/main.js"></script>
If you look closely at that main.js, you will find something like this
ReactDOM.createRoot(document.getElementById("root")).render(<App />);
This is where React takes over. It runs your components, converts JSX(JavaScript XML) into JavaScript objects, what we call the Virtual DOM, and then converts those JavaScript objects into actual DOM nodes the browser can display.
So your JSX that looks like this:
jsx
`<h1 className="title">Hello World</h1>`
gets converted into a Javascript object like this:
js
{
type: "h1",
props:{
className: "title",
children:"Hello World"
}
}
That's your virtual DOM right there, just a plain JavaScript object describing what the UI should look like. React then takes that object and creates an actual DOM node from it:
js
HTMLHeadingElement: node.textContent = "Hello World"`
And appends it to the #root div. Boom, the browser displays it. That's basically CSR.
State updates and reconciliation
Now a huge part of any UI is state: counter, cart items, form inputs, all of that. When state updates, React doesn't re-download main.js and redo everything from scratch; that would be crazy expensive.
What React actually does is look at the component where the update happened, build a new virtual DOM, compare it to the previous one, find what changed, and update only those nodes in the actual DOM. The process is called reconciliation and it's honestly wild how all of this happens without a full page refresh.
And that whole process I just described, the browser downloading JS, React building the virtual DOM, reconciliation and all — that's Client Side Rendering.
Then SSR came along
SSR is so beautiful because it offloads a huge chunk of this work from the browser and just does it on the server instead.
Now "the server" can sound confusing, but here is the thing, JavaScript can run in two primary environments: the browser and Node.js. So in SSR, it's Node.js doing all the heavy lifting.
Here's what happens when you request a page:
The Node.js server runs React, calls your component function, parses JSX, builds the virtual DOM and converts everything into readable HTML, then sends that fully structured HTML straight to the browser. The browser receives real HTML and can display it immediately, no waiting for JS to build everything from scratch.
html
<div id="root">
<h1>Hello</h1>
<p>This came from the server</p>
</div>
But that's not the end of it. In the background the JavaScript bundle also loads, and once it does, hydration happens.
Okay but what even is hydration?
Hydration is basically the process of attaching interactivity, specifically event listeners, to the HTML the server already sent. Think of it like the server sending you a fully built house, and hydration is the electrician coming in afterwards to wire everything up.
In CSR, React uses createRoot which builds everything from scratch. In SSR, React uses hydrateRoot which assumes the DOM already exists, all it does is recreate the virtual DOM and attach event handlers to the right elements.
js// CSR - builds from scratch
ReactDOM.createRoot(document.getElementById("root")).render(<App />);
// SSR - assumes HTML exists, just wires it up
ReactDOM.hydrateRoot(document.getElementById("root"), <App />)
So How does this affect how I write Next.js Code
Next.js does SSR by default. Every component runs on the server first, even ones marked with "use client" directive. The difference is that "use client" tells the compiler that this component needs to be hydrated on the client so it can handle interactivity.
So the rule is simple, anywhere you need state, effects, browser APIs like document.querySelector or even event listeners, add "use client" at the top of the file. If you don't, your code will be trying to access browser APIs that don't exist on the server and you will be staring at errors wondering what went wrong.
"use client";
import { useState } from "react";
export default function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}
So what does this mean for how I build components
Honestly this is the part that changed how I think at work. Once you understand CSR and SSR, you stop throwing use client everywhere and start asking, does this component actually need to run on the client?
The mistake I made early on was throwing use client at the top of every component. It just felt like the thing to do coming from plain React. But once I understood what was actually happening under the hood, I realized I was basically opting out of SSR everywhere and losing all the performance benefits for free.
A good rule of thumb, if a component just displays data with no clicks, no state, no user interaction, leave it as a server component. Only reach for use client when the component actually needs to respond to the user. That way the browser only handles what it truly needs to.
Wrapping up
It's honestly crazy how much is happening under the hood every time a page loads. Understanding this whole flow — CSR, virtual DOM, reconciliation, SSR, hydration — really changed how I think about building components and where I put my logic.
Let me know what part clicked for you or if there's anything you'd push back on — I'd really love to hear it.
See you next time. Byeeee


Top comments (0)