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


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:

  1. Export dynamic = "force-dynamic" in page.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

  1. Server-Side Rendering (SSR) generates fresh HTML on Node.js servers for every incoming HTTP request.
  2. export const dynamic = 'force-dynamic' opts out of static build caching for the route segment.
  3. 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:

  1. Import cookies and headers from next/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

  1. cookies() and headers() from next/headers provide access to incoming HTTP request metadata during SSR.
  2. Invoking cookies() or headers() automatically switches the page segment from static to dynamic SSR rendering.
  3. 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:

  1. 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

  1. Next.js App Router uses HTML Streaming to deliver fast initial shell HTML while slow server components render in background streams.
  2. <Suspense> streams fallback HTML first, then streams final component HTML over the open HTTP connection when data resolves.
  3. Reduces TTFB (Time-To-First-Byte) latency significantly.


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.
Built with LogoFlowershow