
Deconstructing React’s Render Pipeline - From State Change to Screen Paint
- Published on
- - 19 min read
A beginner-friendly mental model for understanding Client-Side Rendering (CSR), Static Site Generation (SSG), Server-Side Rendering (SSR), hydration, and React Server Components (RSC) without mixing them up.

I used to look at terms like Client-Side Rendering (CSR), Static Site Generation (SSG), Server-Side Rendering (SSR), and React Server Components (RSC) and feel like they were all trying to explain the same thing in different words. Everything sounded like some variation of React running somewhere.
But that was exactly where my confusion started.
The moment things became clearer was when I stopped asking one big vague question:
Is this server-side?
And instead started asking two smaller questions:
That small shift changed everything.
The confusing part is that these terms are closely related, but they do not answer the same question.
It is tempting to think:
SSG, SSR, and RSC all involve the server, so they must be variations of the same concept.
However, that is not quite right.
SSG and SSR are page rendering strategies. They describe when the page is generated.
RSC is a component model. It describes where a component runs and whether its JavaScript is sent to the browser.
That difference is the foundation. Think of a restaurant.
SSG asks:
Was the meal prepared before the customer arrived?
SSR asks:
Was the meal prepared after the customer ordered?
RSC asks something different:
Which parts should stay in the kitchen, and which parts must go to the customer’s table?
Same restaurant. Different questions.

Once this distinction becomes clear, everything else becomes much easier.
Before understanding SSG, SSR, or RSC, we need to start with the version of React most of us learn first: CSR, or Client-Side Rendering. In CSR, the browser does most of the work.
Here is the basic flow:
This is simple and powerful. It is why React feels so natural for dashboards, admin panels, calculators, forms, and highly interactive apps. Once the JavaScript is loaded, the app can feel smooth because navigation and UI updates happen inside the browser.
But there is a problem. The browser has to download and run JavaScript before the user gets the real experience. Imagine ordering furniture online and receiving raw wood, screws, tools, and an instruction manual. You can absolutely build the table yourself, but you cannot use it immediately.
That is CSR. The user gets the materials. The browser builds the page.
CSR works well for many apps, but it creates two common problems. First, users may see a blank screen while JavaScript loads. Second, search engines and social previews may not immediately see the useful page content if that content only appears after JavaScript runs.
So naturally, developers asked:
Why make every browser build the same page from scratch?
That question leads us to SSG and SSR.
SSG, or Static Site Generation, means the page is generated before the user requests it. Usually, this happens at build time.
Let’s say you have a blog with these pages:
/about
/blog/react-basics
/blog/ssr-vs-rsc
/contactWith SSG, your framework generates the HTML for these pages when you build or deploy your site. Later, when a user visits /blog/ssr-vs-rsc, the server does not need to generate that page from scratch. It simply sends the already-created HTML.
This is like preparing food before customers arrive. If you run a bakery, you do not bake one cookie from scratch every time someone walks in. You bake a tray in advance. When customers come, you serve quickly.
That is SSG.

The problem SSG solves is simple:
Why repeatedly generate pages that rarely change?
If a blog post changes once a week, generating it on every request would be wasteful. SSG lets you do the work once and reuse the result for many users.
That makes SSG great for:
But SSG has a tradeoff. If the data changes after the build, the page may become stale until you rebuild or revalidate it. That is why SSG is usually not the best fit for things like:
SSG is fast because the page is already made. But that speed comes from the assumption that the page does not need to be freshly generated for every user.
So SSG answers this question:
Can I generate this page before the user asks for it?
If yes, SSG is often a great choice.
But what if the page must be fresh for every request? That is where SSR comes in.
SSR, or Server-Side Rendering, means the server generates HTML when the user requests the page.
This is different from SSG.
With SSG, the page is generated before the request.
With SSR, the page is generated during the request.
Here is the flow:
SSR solves one of the biggest CSR problems: the blank screen.
Instead of making the browser build the page from scratch, the server sends already-formed HTML.
Think of ordering food at a restaurant. With CSR, the restaurant gives you ingredients and asks you to cook. With SSR, the restaurant cooks the meal after you order and serves it ready.
That is much better for the first experience.

SSR is useful when content needs to be fresh per request. For example:
But SSR has one important catch. The page can appear quickly, but it may not be interactive immediately.
Why? Because the browser still needs JavaScript, and that brings us to one of the most important concepts in this entire topic.
Hydration is the process where React attaches JavaScript behavior to HTML that already exists on the page. This part confused me for a while. I thought:
If SSR already gives the browser HTML, then isn’t the page done?
Not quite. HTML can show content, but HTML alone does not know how to manage React state, event handlers, effects, or client-side interactions.
For example, the server may send this:
<button>Like</button>The browser can display that button immediately. But if this button needs React state, such as:
<button onClick={() => setLikes(likes + 1)}>Like</button>then React has to load in the browser and connect that behavior to the existing HTML. That connection process is hydration.
A simple mental model:
SSR sends a car that looks complete. Hydration installs the controls so you can actually drive it.

This is why SSR improves the first visual experience, but it does not automatically remove JavaScript from the browser.
The browser still downloads JavaScript for the page. The browser still runs React. The browser still hydrates interactive components. And on complex pages, hydration can become expensive.
(Note: This is also where you might encounter the dreaded Hydration Mismatch error. A mismatch happens when the HTML rendered on the server differs from what React expects when it wakes up in the browser—often caused by trying to render browser-only data like window.innerWidth on the initial load!)
SSR answered:
How do we show content sooner?
RSC asks a different question:
Why are we sending JavaScript for parts of the page that never needed JavaScript?
That question changes the whole model.
RSC, or React Server Components, lets some React components run only on the server.
The important part is this:
Server Components do not send their component JavaScript to the browser.
That is the breakthrough.
Let’s say you have a blog post page:
export default async function BlogPost() {
const post = await getPost()
return (
<article>
<h1>{post.title}</h1>
<p>{post.author}</p>
<div>{post.content}</div>
<LikeButton />
</article>
)
}The title, author, and content are mostly static. They do not need useState. They do not need useEffect. They do not need onClick. They do not need browser APIs. So why should the browser receive JavaScript for them? That is the problem RSC solves.
With RSC, static or server-only parts can stay on the server. The browser receives the result, not the component’s JavaScript. Only the interactive parts, like LikeButton, need to become client-side JavaScript.

This is why RSC is not the same as SSR.
SSR says:
Render the page on the server first, then hydrate it in the browser.
RSC says:
Some components should never go to the browser as JavaScript at all.
SSR improves when the user sees the page. RSC improves how much JavaScript the user needs to download and run.
Once RSC enters the picture, we need a new mental model.
In traditional React, we mostly think:
Everything is a component.
With RSC, we ask:
Is this a Server Component or a Client Component?
A Server Component runs on the server.
Use Server Components for:
A Client Component runs in the browser.
Use Client Components for:
window, document, or local storageThis is where the "use client" directive comes in.
"use client"
import { useState } from "react"
export function LikeButton() {
const [liked, setLiked] = useState(false)
return (
<button onClick={() => setLiked(true)}>{liked ? "Liked" : "Like"}</button>
)
}The "use client" line tells React:
This component needs to run in the browser.
(Crucial detail: "use client" does not mean the component skips the server! Client Components are still pre-rendered on the server to HTML for the initial SSR pass. The directive simply means "this component will also send its JavaScript to the browser to hydrate and run.")
That does not mean the whole app must become client-side.
It means this boundary and everything inside it becomes part of the client-side bundle. However, you can still pass Server Components inside a Client Component using the children prop (sometimes called the "hole-in-the-donut" pattern). This prevents everything inside the Client Component from strictly needing client-side JavaScript.
This is powerful, but it requires discipline.
If you put "use client" too high in the tree, you may accidentally send much more JavaScript than needed.
A good rule is:
Keep components server-side by default. Move only the interactive leaves to the client.
Think of a tree. The trunk and branches can stay on the server. Only the fruits that users touch need to be Client Components.
Now we can finally answer the question that caused most of my confusion:
Is SSG the same as RSC?
No.
They are different layers.
SSG and SSR describe when the page is generated.
RSC describes where components run.
That means they can work together.
You can have:
Let’s make this concrete.
Imagine a documentation page.
The docs content changes rarely, so SSG makes sense. You can generate the page at build time.
But inside that page, the article body does not need JavaScript. That part can be a Server Component.
If there is a search box or feedback button, that part can be a Client Component.
So the page can be:
Generated with SSG, built mostly with Server Components, and enhanced with a few Client Components for interactivity.
Now imagine a personalized dashboard.
The data changes per user, so SSR may make more sense.
But even there, not every component needs browser JavaScript. A static sidebar, header, or server-rendered data table may be a Server Component. A chart filter or dropdown may be a Client Component.
So the page can be:
Generated with SSR, built with Server Components where possible, and Client Components where interaction is needed.
This is the mental model that finally made it click for me.
SSG is not competing with RSC. SSR is not exactly competing with RSC either. RSC is a component-level decision that can exist inside different page generation strategies.
There is one more idea that fits naturally here: streaming.
Streaming means the server does not have to wait until the entire page is ready before sending something to the browser; instead, it can send parts of the UI as they become ready.
Why does this matter? Because real apps often have slow data. Maybe the header is ready immediately, but the recommendations section needs a slow API call. Without streaming, one slow section can block the whole page. With streaming, the server can send the ready parts first and show a fallback for the slow part.
import { Suspense } from "react"
export default function Page() {
return (
<>
<Header />
<Suspense fallback={<p>Loading recommendations...</p>}>
<Recommendations />
</Suspense>
</>
)
}Here, the user can see the header quickly while recommendations load. This is like a restaurant serving drinks and appetizers first instead of making everyone wait until the entire meal is finished.
Streaming does not remove all performance problems. It simply gives us a better way to decide what must be ready immediately and what can arrive later.
That decision is a big part of modern React performance.
At this point, RSC can sound like a magic solution: less JavaScript, server-side data fetching, no hydration for Server Components, and better performance. But it is not magic. It introduces tradeoffs.
The first tradeoff is mental complexity. You now need to understand the difference between Server Components and Client Components. You need to know where your code runs. You need to remember that browser-only APIs cannot run in Server Components.
The second tradeoff is server dependency. If a Server Component waits on a slow API, the server-rendered output can be delayed. Streaming and Suspense can help, but they do not make slow data disappear.
The third tradeoff is document size. If you render a lot of content on the server, the HTML or streamed output can become larger. That may still be a good tradeoff if it avoids shipping heavy JavaScript, but it is still a tradeoff.
The fourth tradeoff is framework dependency. Traditionally, SSG and SSR were framework-level features built into tools like Next.js or Astro. RSC, however, is a React-level architecture, but it still heavily relies on a bundler and framework (like the Next.js App Router) to stitch the server and client graphs together.
Finally, what about data mutation? If Server Components only run once to display data, how does the user send data back (like submitting a form)? This is where Server Actions come in—allowing components to securely call server-side functions without having to manually set up API endpoints.
So the right question is not:
Should everything be RSC?
The better question is:
Which parts of this page truly need to run in the browser?
That question leads to better architecture.
When building a page, I now think in this order.
First, I ask:
Is this page mostly static?
If yes, I consider SSG. A blog post, docs page, marketing page, or changelog probably does not need request-time rendering every time.
Then I ask:
Does this page need fresh data for each request?
If yes, I consider SSR. A personalized profile, account page, dashboard, or request-specific product page may need server work at request time.
Then I ask:
Which components actually need interactivity?
If a component only displays text, images, markdown, product details, or server-fetched data, I try to keep it as a Server Component.
If it needs state, effects, event handlers, forms, or browser APIs, I make it a Client Component.
Finally, I ask:
Is any data slow enough that it should be streamed behind a fallback?
If yes, I reach for Suspense and streaming. This gives me a clean path instead of a confusing pile of acronyms.

This is the point where the picture becomes clear.
CSR, SSG, SSR, and RSC are not enemies.
They are tools at different layers.
Here is the simplest version I keep in my head:
That is it. The hard part was not memorizing definitions. The hard part was realizing that these concepts answer different questions.
SSG and SSR ask:
When do we generate the page?
RSC asks:
Which components actually need to become browser JavaScript?
Once I separated those two questions, the confusion disappeared.
The journey from CSR to SSG to SSR to RSC is really the story of moving work to the most efficient place. CSR gave us rich interactivity. SSG gave us prebuilt speed. SSR gave us fresh server-rendered HTML. RSC gave us a way to avoid sending JavaScript for parts of the UI that never needed it.
The best React apps are not built by picking one approach and using it everywhere. They are built by asking the right question at each layer.
SSG and SSR decide when the page is generated. RSC decides which components need to become browser JavaScript.