SSR vs SSG vs ISR: Choosing the Right Rendering Strategy for React

· 11 min read · Frontend Development

A framework-agnostic comparison of server-side rendering, static site generation, and incremental static regeneration with real performance data.

SSR vs SSG vs ISR: Choosing the Right Rendering Strategy for React

Overview

Rendering strategy is one of the most consequential architecture decisions in a React project. Get it right and your Largest Contentful Paint drops below 1 second. Get it wrong and you are fighting framework complexity for years. This post breaks down when to use each approach.

Problem

A single-page React app (SPA) ships an empty HTML shell. The browser downloads JavaScript, executes it, fetches data, and then renders content. Google's crawler waits for this — up to a point. But Core Web Vitals suffer because the user stares at a blank screen.

Solutions exist: pre-render the HTML. But the three approaches — SSR, SSG, and ISR — have dramatically different trade-offs.

Solution

Static Site Generation (SSG)

How it works: HTML is generated once at build time. Every user gets the same pre-built file.

Build time:  Fetch data → Render React → Write HTML file
Request:     CDN serves static HTML → Browser hydrates with JS

When to use it:

  • Content changes infrequently (blog posts, docs, marketing pages)
  • Page count is manageable (under ~10,000 pages)
  • Fastest possible TTFB is critical

Real performance:

  • TTFB: 10–50ms (served from CDN edge)
  • LCP: 0.5–1.2s (content is in the HTML)
  • Build time: scales linearly with page count

The problem: When content changes, you rebuild and redeploy. For a blog with 50 posts this takes seconds. For an e-commerce site with 100,000 products, it takes hours.

Server-Side Rendering (SSR)

How it works: HTML is generated on every request. The server fetches data, renders React, and streams the result.

Request:  Server receives request → Fetch data → Render React → Send HTML
          Browser receives HTML → Hydrates with JS

When to use it:

  • Content is user-specific (dashboards, authenticated pages)
  • Data changes frequently (real-time prices, inventory)
  • SEO is critical AND content is dynamic

Real performance:

  • TTFB: 100–500ms (depends on data fetching + render time)
  • LCP: 1.0–2.5s (HTML arrives later but is complete)
  • No build-time scaling issue

The problem: Every request requires server compute. Cold starts on serverless platforms add 500ms+. Caching helps, but then you are reinventing SSG.

Incremental Static Regeneration (ISR)

How it works: Pages are generated statically, but can be re-generated in the background after a configurable interval.

First request:     Serve stale static HTML → Trigger background regeneration
Second request:    Serve newly generated HTML

When to use it:

  • Content updates are frequent but not real-time
  • You want SSG performance with SSR freshness
  • Large page counts make full rebuilds impractical

Real performance:

  • TTFB: 10–50ms (same as SSG — serving static files)
  • LCP: 0.5–1.2s
  • Staleness: configurable (60s, 5min, 1hr)

The problem: You accept serving potentially stale content. Not acceptable for prices, inventory, or anything where a user might act on outdated information.

Implementation

Decision Matrix

Factor SSG SSR ISR
TTFB Best Worst Best
Content freshness Build-time only Real-time Near real-time
Server cost Zero (CDN) Per-request Minimal
Build time Scales with pages None None
SEO Excellent Excellent Excellent
Personalization None Full Limited

Hybrid Approach (What Works Best)

Most production apps should not pick one strategy — they should mix them:

  • SSG for marketing pages, docs, blog index
  • ISR for individual blog posts (revalidate every 5 min)
  • SSR for authenticated dashboards, search results
  • CSR (client-side) for highly interactive UI after initial load

This is exactly how this portfolio works:

  • Homepage: SSG with pre-rendered HTML injected at build time
  • Blog posts: SSG with content fetched from Supabase at build
  • Admin dashboard: CSR behind authentication

SSG Without a Framework

You do not need Next.js for SSG. A Vite + React SPA can achieve the same result with a build-time prerender script:

// scripts/prerender.ts
const routes = ["/", "/blog", "/contact"];

for (const route of routes) {
  const html = injectMetaTags(template, routeMetadata[route]);
  const staticContent = generateStaticHTML(route);
  const final = html.replace(
    '<div id="root"></div>',
    `<div id="root">${staticContent}</div>`
  );
  writeFileSync(`dist${route}/index.html`, final);
}

React's createRoot replaces the static content on hydration. Crawlers see full HTML. Users see content immediately.

Challenges

Hydration mismatch: If your SSG HTML does not match what React renders on the client, you get hydration errors. Common causes: date formatting, random IDs, browser-only APIs. Solution: use deterministic rendering and guard browser APIs with typeof window checks.

Cache invalidation with ISR: When you update a blog post, the old version might be served for up to revalidate seconds. For critical content, use on-demand revalidation (webhook triggers rebuild of specific pages).

Conclusion

SSG is the right default for content sites. It is the fastest, cheapest, and simplest. Add ISR when content freshness matters. Reach for SSR only when personalization or real-time data is required. Most apps need a hybrid — not a single strategy.