Back to Performance
Performance
medium
very high
mid

When should you use SSR, SSG, or CSR for a web application?

CSR ships JS and renders in the browser (best for app-like, auth'd UI). SSR renders per request on the server (best for personalized, fresh content). SSG pre-renders at build time (best for marketing/blogs). ISR adds incremental revalidation on top of SSG.

7 min read·~15 min to think through

Four common strategies, each trading freshness for cost and TTFB for HTML readiness:

  • CSR (Client-Side Rendering) — server returns a near-empty HTML shell; JS hydrates and renders. Pros: simple infra, app-like UX. Cons: slow LCP, bad SEO without extra effort.
  • SSR (Server-Side Rendering, per request) — server renders to HTML on every request. Pros: fast meaningful paint, fresh data, SEO-friendly. Cons: server cost, slower TTFB than static, requires running infra.
  • SSG (Static Site Generation, build time) — render once at build, serve from CDN. Pros: cheap, instant TTFB, infinitely scalable. Cons: data is build-time stale; rebuild cost grows with page count.
  • ISR / Revalidation — SSG + a stale-while-revalidate window. Best of both for content that changes occasionally (blogs, product pages).

Decision heuristic:

  • Marketing / docs / blog → SSG (or ISR if frequent updates).
  • E-commerce product page → ISR (cheap, mostly fresh).
  • Logged-in dashboard → CSR for the app shell + server-data per route. RSCs (React Server Components) blur the lines further.
  • Personalized SSR → only when SEO + per-user freshness both matter (e.g., logged-in homepage that's also indexed).

Modern Next.js / Remix lets you pick per route. Don't pick one strategy for the whole app — pick per page based on what that page actually needs.

Code

tsx
// app/blog/page.tsx — SSG (default for static data)
export default async function Blog() {
  const posts = await getPosts(); // build-time
  return <List posts={posts} />;
}

// app/product/[id]/page.tsx — ISR
export const revalidate = 60; // seconds

// app/feed/page.tsx — SSR (per-request)
export const dynamic = "force-dynamic";
Next.js App Router — three strategies in one app

Follow-up questions

  • How do React Server Components change the SSR vs CSR conversation?
  • What's the cost model of SSR at scale and how do you cap it?
  • When does PPR (Partial Prerendering) make sense?

Common mistakes

  • Using CSR for content pages and tanking SEO + LCP.
  • Using SSR everywhere and paying for compute on pages that could be static.
  • Treating ISR cache as fresh — there's always a stale window.

Performance considerations

  • SSG/ISR served from CDN edge ≈ <50ms TTFB. SSR is bound by your origin region.
  • CSR's LCP is gated by JS download + parse + render — every KB matters.

Edge cases

  • Cookies/personalization break CDN caching — opt out with `cache: 'no-store'` or per-user keys.
  • ISR's stale window can show outdated data for that first request after invalidation; design copy that tolerates it.

Real-world examples

  • Vercel/Next docs: SSG. Linear web app: CSR shell + RSC for data. Amazon product page: ISR-like edge cache + SSR for personalized blocks.

Senior engineer discussion

Senior signal: discuss the cache hierarchy (browser, edge, ISR, ORM), per-route cost modeling, RSC streaming, and partial prerendering as the future hybrid.

Related questions