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.
| Strategy | When HTML is built | Freshness | Typical use |
|---|---|---|---|
| SSG | Build time | Until redeploy | Marketing, docs, legal |
| ISR | Build + revalidate | Eventual (timer or on-demand) | Blog, product catalog |
| SSR | Every request | Always fresh | Dashboards, checkout |
| CSR | In the browser | Client fetch | Admin 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 cachingReal-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'andforce-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:
| Export | Effect |
|---|---|
dynamic | 'auto' | 'force-static' | 'force-dynamic' | 'error' |
revalidate | ISR interval in seconds (segment-level) |
dynamicParams | false → 404 for slugs not in generateStaticParams |
fetchCache | Override 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 ISRFetch colocates with UI in async Server Components — no separate data export.
Interview:
generateStaticParamsprebuilds paths at deploy.dynamicParams = falseis 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 pattern | Pick |
|---|---|
| SEO + scale + slow change | SSG or ISR |
| Per-user personalization | SSR or dynamic holes (PPR) |
| Real-time private UI | CSR client fetch |
| Flash sale pricing | Short 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
privatecaching 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