Rendering Basics

Four Ways to Make HTML

"Rendering" means turning components into HTML. The question is where and when it happens:

NameWhereWhenGood for
CSR — Client-Side RenderingBrowserAfter JS downloadsHighly interactive widgets, dashboards
SSR — Server-Side RenderingServerOn every requestPersonalised pages, cart, search results
SSG — Static Site GenerationServerOnce, at build timeAbout page, blog posts, docs
ISR — Incremental Static RegenerationServerAt build, then again every N secondsProduct pages, news, prices that change slowly

A Timeline

SSG:  npm run build ──> HTML saved ──> every user gets the same file (fast)
SSR:  user request ──> server fetches data ──> builds HTML ──> response
ISR:  build ──> HTML cached ──> after 60 s the next request triggers a rebuild in background
CSR:  user request ──> empty HTML + JS ──> browser fetches data ──> builds page

ISR in Plain TypeScript

Example

Next.js decides this for you, but the idea of ISR is simple: serve the cached page, and if it is older than revalidate seconds, rebuild it. Here time is passed in so the output is fixed:

type Cached = { html: string; builtAt: number };

let cache: Cached | null = null;
let builds = 0;
const REVALIDATE = 60; // seconds

function render(): string {
  builds++;
  return '<h1>Price list v' + builds + '</h1>';
}

function getPage(now: number): string {
  if (!cache || now - cache.builtAt >= REVALIDATE) {
    cache = { html: render(), builtAt: now };
    return cache.html + '  (rebuilt at t=' + now + ')';
  }
  return cache.html + '  (from cache, age ' + (now - cache.builtAt) + 's)';
}

for (const t of [0, 10, 59, 60, 75, 130]) {
  console.log('t=' + t + ': ' + getPage(t));
}

Output

t=0: <h1>Price list v1</h1>  (rebuilt at t=0)
t=10: <h1>Price list v1</h1>  (from cache, age 10s)
t=59: <h1>Price list v1</h1>  (from cache, age 59s)
t=60: <h1>Price list v2</h1>  (rebuilt at t=60)
t=75: <h1>Price list v2</h1>  (from cache, age 15s)
t=130: <h1>Price list v3</h1>  (rebuilt at t=130)

Note: Real Next.js ISR serves the stale page once and rebuilds in the background (stale-while-revalidate). The example rebuilds immediately to keep it simple.

In the App Router

You do not pick a mode with a special function. Next.js chooses static (SSG) by default, and switches to dynamic (SSR) when your page reads request data like cookies or search params. ISR is one line: export const revalidate = 60. You will learn the details in the Medium level (Caching and Revalidation).