Server-Side Rendering (SSR) Overview
Server-Side Rendering (SSR) Overview
Level 1 — Core Concepts & Architecture The process of generating the full HTML for a web page on the server dynamically at request time, before sending it to the browser.
1. Prerequisites
- Next.js Overview — The framework that makes SSR easy.
- React Server Components (RSC) — RSCs are often part of the SSR pipeline.
- Hydration — The process that makes static HTML interactive.
- SEO (Search Engine Optimization) — The search engine requirements that SSR satisfies.
2. Term Category
Rendering Strategy (Server-Side Rendering): Server-Side Rendering (SSR) generates full HTML on the server for each HTTP request before delivering static mark-up to browsers.
3. Explanation
Environment Context
- Server Only
(1) Design Motivation — "Why did we design this?"
In a standard React App (Client-Side Rendering or CSR), the server sends an empty HTML file: <div id="root"></div>. The browser must then download React, execute it, fetch data, and then paint the UI.
If a search engine bot (like Google) hits that page, it sees nothing. If a user on a slow 3G connection hits that page, they stare at a white screen for 5 seconds.
Server-Side Rendering (SSR) fixes this. When a user requests a URL, the Server runs React, fetches the database data, generates the full, populated HTML string (e.g., <h1>Welcome Alice</h1>), and sends that to the browser. The user sees the content instantly, and SEO bots are happy.
(2) SSR in the App Router
In the modern Next.js App Router, SSR is seamless. If you create a Server Component that fetches data dynamically (e.g., reading cookies, or making a fetch request that isn't cached), Next.js automatically Server-Side Renders that route on every single request.
// app/dashboard/page.tsx
import { cookies } from 'next/headers';
export default async function Dashboard() {
// Because we are reading a request-specific cookie, Next.js knows
// it CANNOT pre-build this page. It must SSR this page on every request!
const session = cookies().get('session');
const user = await fetchUser(session.value);
// This HTML is generated dynamically on the server and sent to the browser
return <h1>Welcome to your secure dashboard, {user.name}</h1>;
}
(3) Hydration
SSR only generates static HTML. The browser displays it instantly, but it is "frozen" (buttons won't work). Next.js simultaneously sends the React JavaScript bundle to the browser in the background. Once the JS loads, it attaches event listeners to the static HTML, making it fully interactive. This process of bringing the static HTML "to life" is called Hydration.
4. Common Mistakes & Pitfalls
Mistake 1: Hydration Mismatch
The mistake: A developer writes code that renders different things depending on where it runs:
"use client"
export default function Clock() {
// ❌ ERROR: Hydration failed because the initial UI does not match what was rendered on the server.
return <div>{new Date().getTime()}</div>;
}
Why it's wrong: During SSR, the server generates the HTML using the time on the server (e.g., 12:00). When the browser downloads the HTML and runs Hydration 2 seconds later, it generates 12:02. React compares the Server HTML to the Browser HTML, sees a mismatch, panics, and throws a massive error.
Golden Rule: The initial render of a Client Component MUST perfectly match between the Server and the Client. Use useEffect if you need to render something strictly client-side (like browser timestamps or window.innerWidth).
Mistake 2: Using cache: 'no-store' Unnecessarily on Static Pages (High Server CPU Load)
The mistake: Configuring fetch(url, { cache: 'no-store' }) on static marketing pages.
Why it's wrong: Disabling cache forces Next.js to re-render the page dynamically on EVERY single HTTP request, inflating server CPU usage and slowing response times. Use ISR or static caching.
Incorrect:
// Fetching static data with no-store
fetch('https://api.com/terms', { cache: 'no-store' }); // ❌ Forces dynamic server render on every hit!
Fix:
// Allow Next.js to cache static data:
fetch('https://api.com/terms', { next: { revalidate: 86400 } }); // Revalidate daily
Mistake 3: Confusing Server-Side Rendering (SSR) with Static Site Generation (SSG)
The mistake: Expecting SSR pages to build once at compile time without running Node.js server execution.
Why it's wrong: SSR generates HTML dynamically ON DEMAND during HTTP requests. SSG generates static HTML once at BUILD TIME. Choose the right rendering strategy for your data update frequency.
Incorrect:
/* Expecting SSR routes to serve pre-built static HTML files without server execution */
Fix:
/* Use SSG/ISR for pre-built static pages; use SSR for request-time dynamic data */
5. Practice Exercises
Exercise 1: Enforcing Dynamic Server-Side Rendering
Scenario:
Force a route to execute dynamic Server-Side Rendering (SSR) on every request using export const dynamic = 'force-dynamic'.
Requirements:
- Export
dynamic = "force-dynamic"inpage.tsx.
Answer
Implementation
// app/dashboard/page.tsx
export const dynamic = "force-dynamic";
export default async function RealtimeDashboard() {
const res = await fetch("https://api.example.com/live", {
cache: "no-store"
});
const data = await res.json();
return (
<main className="p-6">
<h1>Realtime Metrics</h1>
<p>Live Users: {data.activeUsers}</p>
</main>
);
}
Technical Explanation
- Server-Side Rendering (SSR) generates fresh HTML on Node.js servers for every incoming HTTP request.
export const dynamic = 'force-dynamic'opts out of static build caching for the route segment.- Essential for user-specific or real-time data dashboards.
Exercise 2: Accessing Server Cookies and Headers during SSR
Scenario: Read incoming request HTTP headers and session cookies on the server during SSR rendering.
Requirements:
- Import
cookiesandheadersfromnext/headers.
Answer
Implementation
import { cookies, headers } from "next/headers";
export default async function ProfilePage() {
const cookieStore = await cookies();
const headersList = await headers();
const token = cookieStore.get("session_token");
const userAgent = headersList.get("user-agent");
return (
<main className="p-6">
<p>Session Active: {token ? "Yes" : "No"}</p>
<p>Browser User Agent: {userAgent}</p>
</main>
);
}
Technical Explanation
cookies()andheaders()fromnext/headersprovide access to incoming HTTP request metadata during SSR.- Invoking
cookies()orheaders()automatically switches the page segment from static to dynamic SSR rendering. - Secure server-side request inspection.
Exercise 3: Streamlining SSR Content with React <Suspense>
Scenario:
Stream heavy server-rendered components using React <Suspense> boundaries to improve Time To First Byte (TTFB).
Requirements:
- Wrap slow Server Component in
<Suspense fallback={...}>.
Answer
Implementation
import { Suspense } from "react";
async function SlowFeed() {
const data = await fetch("https://api.example.com/slow", { cache: "no-store" }).then(r => r.json());
return <div>Feed Loaded: {data.items.length} items</div>;
}
export default function FeedPage() {
return (
<main className="p-6">
<h1>Live User Feed</h1>
<Suspense fallback={<div>Loading Feed...</div>}>
<SlowFeed />
</Suspense>
</main>
);
}
Technical Explanation
- Next.js App Router uses HTML Streaming to deliver fast initial shell HTML while slow server components render in background streams.
<Suspense>streams fallback HTML first, then streams final component HTML over the open HTTP connection when data resolves.- Reduces TTFB (Time-To-First-Byte) latency significantly.
6. Related Terms
- React Server Components (RSC) — RSCs generate the payload that fuels the SSR process.
- Static Rendering (SSG) — The alternative to dynamic SSR.
7. Key Takeaways
- Server-Side Rendering (SSR) means executing React on the server to generate a fully populated HTML string dynamically upon request.
- It solves the primary issues of pure React: bad SEO and slow initial loading screens.
- Hydration is the process where React attaches JavaScript event listeners to the server-generated HTML to make it interactive in the browser.
- If your initial server HTML does not perfectly match your initial browser HTML, React will throw a Hydration Mismatch error.