React Server Components & Rendering

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

React Server Components require a framework with built-in support (such as Next.js's App Router) — they are not something you can add to a plain create-react-app project on your own.

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

CSR typically means a blank screen (or spinner) during the initial JS download and execution, which can hurt both perceived performance and SEO.

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.

Request comes in
Server renders React to HTML
HTML sent to browser (visible immediately)
JS bundle downloads
React hydrates, attaching event handlers

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.

AspectSSRSSG
When rendering happensEvery requestOnce, at build time
Best forPersonalized, frequently-changing pagesMarketing pages, blogs, docs
Server loadHigher (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

ISR is a Next.js-specific feature, though the general concept — "stale-while-revalidate" for whole pages — has analogs in other frameworks under different names.

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

RSC is fundamentally different from SSR: SSR renders Client Components to HTML once, on the server, then still ships their full JS code to the browser for hydration. Server Components never ship their code to the browser at all.

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

Server Components can be async functions directly — no useEffect or loading state needed, since data fetching happens before the component ever reaches the client.

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

If server-rendered HTML doesn't exactly match what the client would render, React logs a hydration mismatch warning and may discard and re-render the affected content.

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

You won't typically interact with React Flight directly; it's the underlying wire format that frameworks like Next.js use to transmit Server Component output to the client.

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

Because Server Components can be async and awaited directly in JSX, complex client-side data-fetching patterns like useEffect chains often disappear entirely for server-rendered content.

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

StrategyInitial loadJS shippedFreshness
CSRSlower (blank until JS runs)Full app bundleAlways live
SSRFast (HTML immediate)Full app bundle (for hydration)Per-request
SSGFastest (pre-built)Full app bundle (for hydration)Build-time only (or via ISR)
RSCFast (HTML immediate)Only Client Component codeConfigurable 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

  1. Default to Server Components; only add "use client" where interactivity is actually needed
  2. Keep Client Components small and near the "leaves" of the tree — a button, not an entire page
  3. Use Suspense boundaries around slow Server Component data fetches to enable streaming
  4. Never pass secrets or server-only logic into a Client Component's props
  5. 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

A very common source of confusion: forgetting that Server Components cannot use hooks like useState, useEffect, or browser-only APIs at all — that logic must live in a "use client" component.

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.

27. 📌 Summary

>>The best rendering strategy is the one matched to what each page actually needs — not a single choice applied uniformly across an entire app.