Topics
v5.0.0
On this page · sections

Rendering Strategies

These four rendering strategies answer one question: when and where is HTML produced? In the App Router, they map to segment config (dynamic, revalidate) and fetch options — not separate data exports.

StrategyWhen HTML is builtFreshnessTypical use
SSGBuild timeUntil redeployMarketing, docs, legal
ISRBuild + revalidateEventual (timer or on-demand)Blog, product catalog
SSREvery requestAlways freshDashboards, checkout
CSRIn the browserClient fetchAdmin tools, post-auth UI

SSG (Static Site Generation)

Definition

SSG generates HTML at build time. Every user receives the same pre-rendered page from the CDN until the next deploy.

// app/about/page.tsx — force-static (default for static routes) export const dynamic = "force-static"; export default async function AboutPage() { return <main>About us — built at deploy</main>; }

Real-world example

Marketing homepages, documentation, and legal pages rarely change. SSG delivers the fastest LCP because HTML is ready at the edge.

export async function generateStaticParams() { const slugs = await fetchAllSlugs(); return slugs.map((slug) => ({ slug })); } export default async function DocPage(props: { params: Promise<{ slug: string }>; }) { const { slug } = await props.params; const doc = await fetchDoc(slug); return <article>{doc.body}</article>; }

Interview: SSG = HTML at build time. Same output for all users until rebuild.

SSR (Server-Side Rendering)

Definition

SSR renders HTML on every request on the server. Content can differ per user, cookie, or header.

// app/dashboard/page.tsx — force-dynamic export const dynamic = "force-dynamic"; export default async function DashboardPage() { const session = await getSession(); return <main>Hello {session.user.name}</main>; }

Senior-level usage

Use SSR when content must be personalized or depends on request context. Accept higher TTFB in exchange for per-request freshness.

import { cookies } from "next/headers"; export default async function CheckoutSummary() { const cartId = (await cookies()).get("cart")?.value; const cart = await fetchCart(cartId); return <OrderSummary items={cart.items} total={cart.total} />; }

Auth gates belong in middleware — redirect before paying SSR cost:

// middleware.ts export function middleware(request: NextRequest) { const token = request.cookies.get("session"); if (!token && request.nextUrl.pathname.startsWith("/app")) { return NextResponse.redirect(new URL("/login", request.url)); } return NextResponse.next(); }

Interview: SSR adds server compute before the first byte. It wins when content must differ per request — not when you need the fastest public LCP.

ISR (Incremental Static Regeneration)

Definition

ISR combines SSG speed with eventual freshness. Pages are statically generated, then regenerated on a timer or on-demand invalidation.

// Segment-level revalidate export const revalidate = 60; export default async function BlogPost(props: { params: Promise<{ slug: string }>; }) { const { slug } = await props.params; const post = await fetchPost(slug); return <article>{post.body}</article>; }

Or per-fetch revalidate:

const products = await fetch(`https://api.example.com/c/${cat}`, { next: { revalidate: 120 }, }).then((r) => r.json());

Real-world example

E-commerce catalogs with thousands of SKUs: serve cached HTML at the edge, accept prices updating within 60–120 seconds.

export const revalidate = 120; export default async function CategoryPage(props: { params: Promise<{ cat: string }>; }) { const { cat } = await props.params; const products = await fetchCategory(cat); return ( <ul> {products.map((p: { id: string; name: string }) => ( <li key={p.id}>{p.name}</li> ))} </ul> ); }

For long-tail paths, combine generateStaticParams with dynamicParams = true so uncached slugs generate on first visit.

Interview: ISR serves cached static HTML until revalidation. It is not SSR — the page is static until the timer fires or a tag is invalidated.

CSR (Client-Side Rendering)

Definition

CSR sends a minimal HTML shell; the browser fetches data and renders content after JavaScript loads.

"use client"; import { useEffect, useState } from "react"; export default function AnalyticsDashboard() { const [data, setData] = useState<Metrics | null>(null); useEffect(() => { fetch("/api/metrics").then((r) => r.json()).then(setData); }, []); if (!data) return <div className="skeleton">Loading…</div>; return <Chart data={data} />; }

Senior-level usage

CSR fits authenticated, SEO-irrelevant zones — internal admin panels, heavy chart dashboards behind an auth gate. Avoid CSR for public landing pages crawlers must index.

Interview: CSR defers work until JS loads. Fine for private UI; wrong for SEO-critical public routes.

Static vs dynamic rendering (App Router)

Definition

App Router routes are static or dynamic based on segment config and which APIs they use. Static routes enter the Full Route Cache; dynamic routes render per request.

// Static — prerendered at build or on first visit (ISR) export const dynamic = "force-static"; export const revalidate = 300; // Dynamic — render on every request export const dynamic = "force-dynamic"; export const revalidate = 0;

Automatic dynamic triggers:

cookies() / headers() / searchParams → route becomes dynamic fetch(..., { cache: 'no-store' }) → that fetch is dynamic noStore() → opts function out of caching

Real-world example

Hybrid product page: static shell with ISR, dynamic stock badge via a client island or short-revalidate fetch.

export const revalidate = 60; export default async function ProductPage(props: { params: Promise<{ id: string }>; }) { const { id } = await props.params; const product = await fetchProduct(id); // cached with ISR return ( <> <h1>{product.name}</h1> <StockBadge productId={id} /> {/* client island — live stock */} </> ); }

Interview: dynamic = 'force-static' and force-dynamic' override heuristics. Most routes are inferred from fetch and dynamic APIs.

Route segment rendering config

Definition

Segment exports control how a route tree renders:

ExportEffect
dynamic'auto' | 'force-static' | 'force-dynamic' | 'error'
revalidateISR interval in seconds (segment-level)
dynamicParamsfalse → 404 for slugs not in generateStaticParams
fetchCacheOverride default fetch caching for the segment
runtime'nodejs' | 'edge'
export const dynamicParams = true; // on-demand ISR for unknown slugs export async function generateStaticParams() { return [{ slug: "getting-started" }, { slug: "deployment" }]; }

Pages Router migration map:

Pages Router App Router ───────────────────────────────────────────────── getStaticProps + revalidate → async RSC + export const revalidate getServerSideProps → dynamic = 'force-dynamic' or noStore() getStaticPaths → generateStaticParams fallback: blocking → dynamicParams = true + on-demand ISR

Fetch colocates with UI in async Server Components — no separate data export.

Interview: generateStaticParams prebuilds paths at deploy. dynamicParams = false is a hard 404 for unknown slugs.

Choosing the right strategy

Definition

Ask three questions before picking a mode:

1. Does SEO need full HTML before JS? 2. How fresh must data be? 3. Is content identical for all users?
Answer patternPick
SEO + scale + slow changeSSG or ISR
Per-user personalizationSSR or dynamic holes (PPR)
Real-time private UICSR client fetch
Flash sale pricingShort ISR window or SSR for critical PDP

Hybrid is common: static marketing shell with a client island for live data (stock ticker, cart count).

CDN, personalization, and auth

Public ISR/SSG → cache at CDN — anonymous users Personalized SSR → Cache-Control: private — per session Partial personalize → static shell ISR + client island for "Hello, Name"

Interview: Personalized HTML must not be shared across users at the CDN. Use private caching or SSR per session.

Senior Interview Summary

SSG

HTML at build time. Fastest LCP for public content that rarely changes.

SSR

HTML per request. Enables cookies, headers, and session-based personalization.

ISR

Static speed with eventual freshness. Timer or on-demand revalidation via tags.

CSR

Browser fetch + render. Use for post-auth UI where SEO is irrelevant.

Static vs dynamic

App Router infers render mode from fetch options and dynamic APIs. Segment exports override when needed.

Best mental model

SSG → "Build once, serve everywhere." ISR → "Static until the timer fires." SSR → "Fresh HTML every visit." CSR → "Shell first, data in the browser."

Practical rule

Marketing / docs → SSG Large catalog / blog → ISR Personalized dashboard → SSR Internal admin charts → CSR Need both speed + user data → PPR or hybrid static shell + client island

Last updated on