Skip to content

CSR vs SSR vs SSG vs ISR Compared

advanced15 min read

The One Question That Changes Everything

Every rendering strategy answers the same question: when does HTML get generated?

  • At build time? (SSG)
  • Per request on the server? (SSR)
  • In the browser after JS loads? (CSR)
  • At build time, then refreshed in the background? (ISR)

That single difference — when — cascades into everything: performance, SEO, caching, infrastructure cost, and user experience. Once you internalize this mental model, picking the right strategy for any page becomes trivial.

Mental Model

Think of a restaurant. SSG is a buffet — food is prepared ahead of time and served instantly. SSR is a made-to-order kitchen — each dish is cooked when you order, fresher but slower. CSR is a meal kit — you get raw ingredients (JavaScript) and cook it yourself in your browser. ISR is a buffet that quietly replaces dishes in the background when they get stale — you always get served instantly, but the food stays reasonably fresh.

Client-Side Rendering (CSR)

The server sends a nearly empty HTML file. Your browser downloads a JavaScript bundle, executes it, and React builds the entire DOM from scratch.

<!DOCTYPE html>
<html>
  <body>
    <div id="root"></div>
    <script src="/bundle.js"></script>
  </body>
</html>

The user sees a blank screen until JS downloads, parses, and executes. Then the app renders, fetches data, and renders again.

Server sends empty HTML
       ↓
Browser downloads JS bundle (200KB+)
       ↓
JS parses and executes
       ↓
React mounts, renders DOM
       ↓
Data fetching begins
       ↓
Re-render with data
       ↓
User finally sees content

The waterfall is brutal. On a slow 3G connection, users can stare at a white screen for 5-10 seconds.

When CSR works: Internal dashboards, authenticated apps where SEO doesn't matter, highly interactive tools (think Figma). The tradeoff is acceptable when your users are on fast connections and the page is behind a login.

When CSR fails: Marketing pages, blogs, e-commerce — anywhere first-paint speed and SEO matter.

Quiz
A CSR app shows a blank white screen for 4 seconds on 3G. What is the primary bottleneck?

Server-Side Rendering (SSR)

The server runs your React components on every request and sends fully-formed HTML. The browser gets real content immediately, then hydrates it with JavaScript to make it interactive.

User requests page
       ↓
Server runs React components
       ↓
Server fetches data (DB, APIs)
       ↓
Server renders full HTML
       ↓
Browser receives HTML (content visible!)
       ↓
JS downloads and hydrates
       ↓
Page becomes interactive

The key insight: the user sees content before JavaScript loads. TTFB is slower (the server does work), but First Contentful Paint is faster because the browser renders HTML immediately — no waiting for JS.

export default async function ProductPage({ params }) {
  const product = await db.product.findUnique({
    where: { slug: params.slug }
  })

  return (
    <main>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
      <AddToCartButton productId={product.id} />
    </main>
  )
}

In Next.js App Router, every component is server-rendered by default. No configuration needed.

The tradeoff: Every request hits your server. No CDN caching (the response depends on the request). Server costs scale linearly with traffic.

Quiz
In SSR, when does the user first see content on screen?

Static Site Generation (SSG)

HTML is generated once at build time. Every user gets the same pre-built HTML, served directly from a CDN.

export async function generateStaticParams() {
  const posts = await db.post.findMany({ select: { slug: true } })
  return posts.map((post) => ({ slug: post.slug }))
}

export default async function BlogPost({ params }) {
  const post = await db.post.findUnique({
    where: { slug: params.slug }
  })

  return (
    <article>
      <h1>{post.title}</h1>
      <div>{post.content}</div>
    </article>
  )
}

At build time, Next.js calls generateStaticParams, gets all slugs, renders each page, and outputs static HTML files. These get deployed to a CDN edge network.

Build time: Generate HTML for every page
       ↓
Deploy to CDN (200+ edge locations)
       ↓
User requests page
       ↓
CDN serves pre-built HTML instantly
       ↓
TTFB: ~20ms (edge-cached)

When SSG shines: Blog posts, documentation, marketing pages, course content — anything where the data changes infrequently and pages are the same for all users.

When SSG breaks: Pages that depend on the request (user-specific data, search results) or content that changes every few minutes. You'd need to rebuild and redeploy for every change.

Quiz
A blog has 10,000 posts using SSG. The author publishes a new post. What happens?

Incremental Static Regeneration (ISR)

ISR is SSG with a refresh mechanism. Pages are statically generated at build time, but they can be regenerated in the background after a configurable time interval.

export const revalidate = 3600

export default async function PricingPage() {
  const prices = await fetchPrices()

  return (
    <main>
      <h1>Pricing</h1>
      {prices.map(price => (
        <PriceCard key={price.id} {...price} />
      ))}
    </main>
  )
}

The revalidate = 3600 tells Next.js: serve the cached static page, but if it's older than 1 hour and someone requests it, regenerate it in the background. The current visitor gets the stale page instantly. The next visitor gets the fresh one.

This is the stale-while-revalidate pattern. Users always get instant responses, and content stays reasonably fresh.

Request 1: Serve static HTML (cached)
Request 2 (after revalidate period): Serve stale HTML, trigger background regen
Request 3: Serve fresh HTML (regenerated)

Next.js also supports on-demand revalidation — you can trigger a rebuild for specific pages via revalidateTag or revalidatePath from a webhook:

import { revalidatePath } from 'next/cache'

export async function POST(request: Request) {
  revalidatePath('/blog')
  return Response.json({ revalidated: true })
}
Quiz
With ISR set to revalidate every 60 seconds, a user visits a page that was last generated 90 seconds ago. What happens?

The Comparison

FactorCSRSSRSSGISR
HTML generationBrowser (runtime)Server (per request)Build timeBuild + background regen
TTFBFast (empty HTML)Slower (server work)Fastest (CDN)Fastest (CDN)
First Contentful PaintSlow (JS required)Fast (real HTML)Fastest (pre-built)Fastest (pre-built)
Time to InteractiveSlowModerate (hydration)Moderate (hydration)Moderate (hydration)
SEOPoor (empty HTML)ExcellentExcellentExcellent
Dynamic dataExcellentExcellentNone (build-time only)Good (stale-while-revalidate)
PersonalizationExcellentExcellentNoneLimited
CDN cacheableJS bundle onlyNo (per-request)FullyFully
Server costMinimalHigh (per request)Build onlyLow
Build timeFastN/ASlow (scales with pages)Moderate

The Decision Framework

Here is the thing most people miss: you don't pick one strategy for your entire app. In Next.js App Router, you pick per route.

Mixing strategies in Next.js App Router

In the App Router, rendering strategy is determined by your data access patterns, not a page-level configuration flag. If a Server Component awaits a database call with no caching, it's SSR. If you add export const revalidate = 60, it becomes ISR. If all data is fetched at build time via generateStaticParams, it's SSG. And Client Components that fetch in useEffect are CSR. A single page can mix all four — the product description is SSG, the price is ISR, the user's cart icon is CSR, and the recommendations are SSR.

What developers doWhat they should do
Use CSR for everything because it is simpler
CSR sends empty HTML, destroying SEO and first-paint performance. Most pages benefit from server-generated HTML.
Use SSR/SSG by default, CSR only for authenticated interactive features
Rebuild entire site for every content change with SSG
ISR gives you SSG performance with near-realtime content updates. No full rebuild needed.
Use ISR with on-demand revalidation for content that changes
Use SSR for static content like blog posts
SSR re-renders on every request, wasting server resources and adding latency for content that rarely changes.
Use SSG or ISR for content that is the same for every user
Assume SSR is always better than CSR
SSR adds server cost and complexity. For internal tools where SEO is irrelevant, CSR is perfectly fine.
Use CSR for highly interactive, personalized dashboards behind auth
Interview Question

FAANG interview question: "You're building an e-commerce site with 50,000 product pages. The product descriptions change rarely, but prices update every 5 minutes. How would you architect the rendering strategy?"

Strong answer: Use ISR for product pages with a 5-minute revalidation interval. Product descriptions and images are static (great for CDN caching), while prices get refreshed in the background. For the cart and user-specific elements, use Client Components that fetch from an API. For the search results page, use SSR since every query produces unique results. This gives you CDN-speed page loads for the 99% case while keeping prices reasonably fresh.

Key Rules
  1. 1CSR: browser generates HTML after JS loads. Fast TTFB, terrible FCP, no SEO.
  2. 2SSR: server generates HTML per request. Great FCP and SEO, but no CDN caching and higher server costs.
  3. 3SSG: HTML generated at build time. Best performance (CDN-served), but content is frozen until rebuild.
  4. 4ISR: SSG with background regeneration. CDN performance with configurable freshness via stale-while-revalidate.
  5. 5Pick the strategy per route, not per app. Most apps use a mix of all four.
  6. 6Default to SSG/ISR for public content, SSR for personalized server-rendered pages, CSR for interactive widgets behind auth.
1/11