1. 📖 Introduction
How and where React renders your UI has changed dramatically over the years — from purely client-side rendering to server-side rendering, static generation, and now React Server Components, a fundamentally new model that blurs the line between server and client code. This tutorial walks through every major rendering strategy, explains how they compare, and covers the modern RSC architecture that frameworks like Next.js build on.
Information
2. 💻 Client-Side Rendering (CSR)
In traditional Client-Side Rendering, the server sends a nearly empty HTML shell, and the browser downloads, parses, and executes JavaScript to build the entire UI. This is how classic create-react-app applications work.
csr-shell.html
<div id="root"></div>
<script src="/bundle.js"></script>
<!-- The browser must download and run bundle.js before anything appears -->Warning
3. 🖥️ Server-Side Rendering (SSR)
With Server-Side Rendering, the server runs React on each request to produce fully-formed HTML, which is sent to the browser immediately — then React "hydrates" it client-side to attach interactivity.
4. 🏗️ Static Site Generation (SSG)
Static Site Generation renders pages to HTML once, at build time, rather than per-request. The resulting files can be served instantly from a CDN, with no server-side rendering work per visitor.
| Aspect | SSR | SSG |
|---|---|---|
| When rendering happens | Every request | Once, at build time |
| Best for | Personalized, frequently-changing pages | Marketing pages, blogs, docs |
| Server load | Higher (per request) | Minimal (pre-built) |
5. ♻️ Incremental Static Regeneration (ISR)
Incremental Static Regeneration combines the speed of static files with the freshness of server rendering: a static page is served instantly, but periodically regenerated in the background after a configured time interval.
Reference
6. 🧬 React Server Components (RSC)
React Server Components are a newer rendering model where certain components run exclusively on the server — never shipping their code to the browser at all — while others remain interactive Client Components, as usual.
Important
7. 🖱️ Client Components
A Client Component is the familiar kind of React component: it renders in the browser, can use state, effects, and browser APIs, and its code is included in the JS bundle sent to users.
LikeButton.jsx
"use client";
function LikeButton() {
const [liked, setLiked] = useState(false);
return <button onClick={() => setLiked(!liked)}>{liked ? "❤️" : "🤍"}</button>;
}8. 🖥️ Server Components
A Server Component renders only on the server — it can directly access databases, the filesystem, or secret API keys, and its output is sent to the browser as pre-rendered content, with zero of its own JS shipped to the client.
ProductList.jsx
// No "use client" directive — this is a Server Component by default
async function ProductList() {
const products = await db.query("SELECT * FROM products");
return (
<ul>
{products.map((p) => <li key={p.id}>{p.name}</li>)}
</ul>
);
}Best Practice
9. 🏷️ "use client"
The "use client" directive, placed at the top of a file, marks that file's components (and everything they import) as Client Components, opting them out of server-only rendering.
UseClientDirective.jsx
"use client";
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>{count}</button>;
}10. 🏷️ "use server"
The "use server" directive marks a function as a Server Action — code that always runs on the server, callable directly from Client Components (see Section 17).
UseServerDirective.jsx
"use server";
async function createPost(formData) {
const title = formData.get("title");
await db.posts.insert({ title });
}11. 🗺️ Rendering Strategies
Modern frameworks let you mix strategies per route or even per component — a marketing homepage might be statically generated, a dashboard server-rendered, and a live chat widget purely client-rendered, all within one app.
12. 💧 Hydration
Hydration is the process where React attaches event handlers and internal state to server-rendered HTML already sitting in the DOM, rather than re-creating that markup from scratch.
Hydration.jsx
import { hydrateRoot } from "react-dom/client";
hydrateRoot(document.getElementById("root"), <App />);Caution
13. 🧩 Partial Hydration
Partial hydration only ships and hydrates JS for the interactive parts of a page — like a "like" button — leaving static content (headings, paragraphs, images) as plain HTML with no attached JS at all. Server Components are what make this possible at scale.
14. 📶 Progressive Rendering
Progressive rendering reveals a page's content incrementally — critical content first, secondary content as it becomes available — rather than requiring the entire page to be ready before showing anything.
15. 🌊 Streaming Rendering
Streaming SSR sends HTML to the browser in chunks as it becomes ready on the server, rather than waiting for the entire page to finish rendering before responding. It pairs directly with <Suspense> boundaries.
StreamingWithSuspense.jsx
function ProductPage({ id }) {
return (
<>
<ProductHeader id={id} /> {/* sent immediately */}
<Suspense fallback={<ReviewsSkeleton />}>
<ProductReviews id={id} /> {/* streamed in once ready */}
</Suspense>
</>
);
}16. ✈️ React Flight
"React Flight" is the internal protocol React Server Components use to serialize the tree of rendered server output — including references to where Client Components should be mounted — and send it to the browser in a compact, streamable format.
Reference
17. 🎬 Server Actions
A Server Action is a function marked with "use server" that a Client Component can call directly — for example, from a form's action prop — without manually setting up an API route.
ServerActionForm.jsx
"use client";
import { createPost } from "./actions";
function NewPostForm() {
return (
<form action={createPost}>
<input name="title" />
<button type="submit">Create Post</button>
</form>
);
}18. 📥 Data Fetching in Server Components
Server Components fetch data directly — with plain await, right inside the component — since they run server-side and never need to expose loading states or network waterfalls to the browser.
ServerComponentFetch.jsx
async function UserProfile({ userId }) {
const user = await db.users.findById(userId); // direct DB access, no API layer needed
return <h1>{user.name}</h1>;
}Tip
19. 🗄️ Caching
Frameworks built on RSC typically cache both the data fetched inside Server Components and the rendered output itself, avoiding redundant work across requests for content that hasn't changed.
20. ♻️ Revalidation
Revalidation controls when cached server-rendered content is refreshed — on a time interval, on-demand after a mutation, or via tags that group related cached data together.
Revalidation.jsx
"use server";
import { revalidatePath } from "next/cache";
async function createPost(formData) {
await db.posts.insert({ title: formData.get("title") });
revalidatePath("/blog"); // refresh the cached blog listing
}21. 🔍 SEO Considerations
Search engine crawlers generally index rendered HTML far more reliably than content that only appears after client-side JS execution. SSR, SSG, and Server Components all produce real HTML upfront, giving a meaningful SEO advantage over pure CSR.
22. ⚡ Performance Comparison
| Strategy | Initial load | JS shipped | Freshness |
|---|---|---|---|
| CSR | Slower (blank until JS runs) | Full app bundle | Always live |
| SSR | Fast (HTML immediate) | Full app bundle (for hydration) | Per-request |
| SSG | Fastest (pre-built) | Full app bundle (for hydration) | Build-time only (or via ISR) |
| RSC | Fast (HTML immediate) | Only Client Component code | Configurable via caching/revalidation |
23. 🧭 Choosing the Right Rendering Strategy
- CSR — internal tools, dashboards behind a login, where SEO doesn't matter
- SSG — marketing sites, blogs, documentation with infrequent content changes
- ISR — content that changes occasionally but doesn't need per-request freshness (e.g. a product catalog)
- SSR — personalized or frequently-changing pages that need fresh data on every request
- RSC — apps wanting minimal client JS, direct backend access, and fine-grained control over what's interactive
24. 🏆 Best Practices
- Default to Server Components; only add "use client" where interactivity is actually needed
- Keep Client Components small and near the "leaves" of the tree — a button, not an entire page
- Use Suspense boundaries around slow Server Component data fetches to enable streaming
- Never pass secrets or server-only logic into a Client Component's props
- Choose caching and revalidation strategy deliberately based on how fresh each piece of data needs to be
25. ⚠️ Common Mistakes
- Adding "use client" to entire pages "just in case," losing the benefits of Server Components entirely
- Trying to use useState or useEffect inside a Server Component, which will throw an error
- Importing a Server Component directly into a Client Component file (must instead be passed as children or props)
- Assuming SSR and Server Components are the same thing — they solve different problems
- Forgetting that server-rendered output must exactly match what the client would produce, causing hydration mismatches
Danger
26. 💬 Frequently Asked Questions
Are Server Components a replacement for SSR?
No — they're complementary. Server Components determine what code ships to the browser at all, while SSR/SSG/ISR determine when and how the resulting HTML is generated.
Can a Server Component render a Client Component?
Yes — this is the standard pattern. A Server Component can import and render a Client Component (though not the reverse, since Client Component files can't directly import Server Component files).
Do I need Next.js to use React Server Components?
Currently, yes in practice — RSC requires framework-level support for bundling, routing, and the server/client boundary. Next.js's App Router is the most widely adopted implementation today.