Skip to main content

Command Palette

Search for a command to run...

CSR vs SSR vs SSG vs ISR โ€” Which Rendering Pattern and When

Day 21 of 40 โ€” React System Design Series

Updated
โ€ข8 min read
CSR vs SSR vs SSG vs ISR โ€” Which Rendering Pattern and When
R

Hi, Iโ€™m Richa โ€” a Senior Frontend Engineer with 5+ years of experience building scalable, production-grade web interfaces for enterprise and consumer applications. I work primarily with React, TypeScript, and modern frontend architectures, focusing on component systems, performance, and maintainability. Most of my experience comes from building real-world products in regulated domains like banking and insurance, where clarity, reliability, and long-term ownership matter more than quick demos. Through this blog, I write about frontend engineering fundamentals, scalable UI design, problem-solving, and the lessons Iโ€™ve learned working on large codebases. My goal is to share practical insights โ€” not shortcuts โ€” for developers who want to grow strong engineering foundations. I also mentor early-career developers and strongly believe that curiosity, asking the right questions, and understanding why something works are more important than memorizing tools. If youโ€™re serious about improving as an engineer, youโ€™re in the right place.

Hey everyone, Richa here! ๐Ÿ‘‹

rendering gif

Day 21. Week 5. Performance week.

And we're starting with the question that trips up almost every senior React interview candidate.

"Walk me through CSR, SSR, SSG, and ISR. When would you use each?"

I've seen devs who have shipped production apps for years stumble on this. Not because they don't know what the letters stand for โ€” but because they haven't thought carefully about why each pattern exists and what tradeoffs you're actually making.

Today we fix that.

rendering gif

The core question โ€” when does HTML get generated?

All four patterns are answering the same question: at what point does the HTML that the browser receives get created?

  • At build time?

  • On every request on the server?

  • In the browser after JS loads?

  • At build time but refreshed occasionally?

That question โ€” and the tradeoffs around it โ€” is what separates the four patterns.

rendering gif


CSR โ€” Client-Side Rendering

What happens:

  1. Browser requests the page

  2. Server returns a near-empty HTML shell: <div id="root"></div>

  3. Browser downloads the JavaScript bundle

  4. JavaScript runs, React renders the UI, data is fetched

  5. User sees content

Browser โ†’ Server โ†’ empty HTML โ†’ download JS โ†’ run JS โ†’ render โ†’ fetch data โ†’ show content

The tradeoff:

  • โœ… Simple to deploy (just static files โ€” Netlify, Vercel, S3)

  • โœ… After first load, navigation is instant (SPA)

  • โœ… Cheap to host

  • โŒ Slow Time to First Contentful Paint (user sees blank screen while JS loads)

  • โŒ SEO is poor (search crawlers see the empty HTML shell, not the content)

  • โŒ Performance on low-end devices is bad (JS bundle runs on the user's device)

When to use it:

  • Dashboards and admin panels (authenticated users, SEO doesn't matter)

  • Internal tools

  • Apps where the content is entirely user-specific (no point indexing it anyway)

  • Prototypes and MVPs

// CSR โ€” Vite + React SPA
// main.tsx
ReactDOM.createRoot(document.getElementById('root')!).render(<App />);

// Data fetched client-side
function DashboardPage() {
  const { data: stats } = useQuery({ queryKey: ['stats'], queryFn: fetchStats });
  return <Dashboard stats={stats} />;
}

SSR โ€” Server-Side Rendering

What happens:

  1. Browser requests the page

  2. Server runs React, fetches data, generates full HTML

  3. Server returns complete HTML with content

  4. Browser displays HTML immediately (fast First Contentful Paint)

  5. JavaScript downloads and "hydrates" โ€” makes the page interactive

Browser โ†’ Server โ†’ fetch data + render HTML โ†’ full HTML โ†’ display immediately โ†’ download JS โ†’ hydrate

The tradeoff:

  • โœ… Fast First Contentful Paint โ€” user sees content immediately

  • โœ… Great SEO โ€” crawlers see full HTML

  • โœ… Works well for personalised content (renders per-request with user data)

  • โŒ Slower Time to First Byte (server has to fetch data and render before responding)

  • โŒ More expensive โ€” you need a running server, not just static files

  • โŒ More complex infrastructure

When to use it:

  • E-commerce product pages (SEO critical, personalised pricing/stock)

  • News articles and blogs where content changes frequently

  • Pages that need user-specific data on first load (inbox, feed)

// SSR โ€” Next.js App Router (React Server Components)
// app/products/[id]/page.tsx

export default async function ProductPage({ params }: { params: { id: string } }) {
  // This runs on the server โ€” no client-side fetch, no loading state
  const product = await productService.getById(params.id);

  return (
    <div>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
      <p>ยฃ{product.price}</p>
    </div>
  );
}

SSG โ€” Static Site Generation

What happens:

  1. At build time โ€” framework runs React, fetches all data, generates HTML files

  2. HTML files are deployed to a CDN

  3. Browser requests the page โ†’ CDN returns pre-built HTML instantly

  4. JavaScript hydrates the page

Build time: fetch data โ†’ render โ†’ generate HTML files โ†’ deploy to CDN
Request time: Browser โ†’ CDN โ†’ pre-built HTML โ†’ instant display โ†’ hydrate

The tradeoff:

  • โœ… Fastest possible load time โ€” HTML served from CDN edge, no server computation

  • โœ… Excellent SEO

  • โœ… Highly scalable โ€” a CDN can handle millions of requests

  • โœ… Cheapest hosting

  • โŒ Content is stale until next build โ€” real-time data is not possible

  • โŒ Build time grows with page count (10,000 product pages = very slow build)

  • โŒ Not suitable for user-specific content

When to use it:

  • Marketing sites and landing pages

  • Documentation sites

  • Blogs (content changes rarely)

  • Product catalogue pages where content changes infrequently

// SSG โ€” Next.js App Router
// app/blog/[slug]/page.tsx

// generateStaticParams tells Next.js which pages to pre-build
export async function generateStaticParams() {
  const posts = await blogService.getAllSlugs();
  return posts.map(post => ({ slug: post.slug }));
}

export default async function BlogPost({ params }: { params: { slug: string } }) {
  const post = await blogService.getBySlug(params.slug);
  return (
    <article>
      <h1>{post.title}</h1>
      <div dangerouslySetInnerHTML={{ __html: post.content }} />
    </article>
  );
}

ISR โ€” Incremental Static Regeneration

What happens:

ISR is SSG with a time-based refresh. Pages are pre-built at build time โ€” but after a set interval, the next request triggers a background rebuild of that page. The stale page is served immediately, and the rebuilt version is served from then on.

Build time: generate HTML โ†’ deploy to CDN
First request: serve pre-built HTML instantly
After revalidate window: next request triggers background rebuild
Subsequent requests: serve fresh rebuilt HTML

The tradeoff:

  • โœ… Near-instant load (from CDN like SSG)

  • โœ… Content stays relatively fresh without a full rebuild

  • โœ… Scales like SSG (CDN, not server)

  • โŒ Content can be stale for up to revalidate seconds

  • โŒ First request after stale window still serves old content (subsequent gets new)

When to use it:

  • E-commerce product pages (prices change, but 60-second staleness is acceptable)

  • News sites (articles update occasionally, not every second)

  • Any SSG content that needs periodic refresh without full rebuilds

// ISR โ€” Next.js App Router
// app/products/[id]/page.tsx

export const revalidate = 60; // rebuild this page at most every 60 seconds

export default async function ProductPage({ params }: { params: { id: string } }) {
  const product = await productService.getById(params.id);
  return (
    <div>
      <h1>{product.name}</h1>
      <p>ยฃ{product.price}</p>
    </div>
  );
}

One line of config. The page is served from CDN instantly. Every 60 seconds, the next request triggers a background rebuild.


Mixing patterns โ€” the real world

Real apps don't use just one pattern. Next.js (and modern React frameworks) let you mix them at the page or even component level.

myapp.com/                  โ†’ SSG  (marketing landing page, never changes)
myapp.com/blog/[slug]       โ†’ ISR  (posts change weekly, 1 hour revalidate)
myapp.com/products/[id]     โ†’ ISR  (prices change, 60 second revalidate)
myapp.com/dashboard         โ†’ SSR  (user-specific, personalised on server)
myapp.com/admin             โ†’ CSR  (internal tool, auth required, SEO irrelevant)

Each route uses the pattern that fits its content and performance requirements.


The decision framework

Ask these three questions:

1. Does this page need to be indexed by search engines?

  • No โ†’ CSR is fine (admin, dashboard, authenticated pages)

  • Yes โ†’ SSG, ISR, or SSR

2. How often does the content change?

  • Never / rarely โ†’ SSG (build once, serve forever)

  • Occasionally (minutes to hours) โ†’ ISR (rebuild periodically)

  • Every request / user-specific โ†’ SSR (render on every request)

3. Is the content personalised per user?

  • No โ†’ SSG or ISR (same HTML for everyone)

  • Yes โ†’ SSR (render per-request with user data)

Pattern HTML created SEO Fresh content User-specific Cost
CSR In browser โŒ Poor โœ… Always โœ… Yes ๐Ÿ’ฐ Cheap
SSR Per request on server โœ… Great โœ… Always โœ… Yes ๐Ÿ’ฐ๐Ÿ’ฐ Server needed
SSG At build time โœ… Great โŒ Stale until rebuild โŒ No ๐Ÿ’ฐ Cheapest
ISR Build + periodic refresh โœ… Great โš ๏ธ Up to N seconds stale โŒ No ๐Ÿ’ฐ Cheap (CDN)

Today's takeaway

movie

These aren't just Next.js implementation details. They're fundamental architecture decisions that affect SEO, performance, infrastructure cost, and user experience.

The senior answer to "which rendering pattern would you use?" is never just one word. It's:

"It depends on whether the content needs SEO, how often it changes, and whether it's user-specific. For this app I'd use ISR for the product pages, SSR for the user dashboard, and SSG for the marketing pages."

That level of thinking โ€” and being able to justify each choice โ€” is what senior interviews are looking for.

rendering done gif

See you tomorrow for Day 22 โ€” Code splitting and lazy loading: React.lazy, Suspense, and dynamic imports.


โ† Day 20 โ€” Monorepo Basics โ†’ Day 22 โ€” Code Splitting and Lazy Loading


Part of the React System Design Series โ€” 40 days, one topic per day.

React System Design โ€” Learning in Public

Part 21 of 36

I'm a React developer with 5.5 years of experience who spent 2 years in Lit.js โ€” and slowly forgot how React really works at a senior level. This series is my public journey back. Every day I cover one topic: state management, authentication, performance, system design interviews, testing โ€” the real stuff that separates a mid-level dev from a senior. Written honestly, with real code, for developers at every level.

Up next

Code Splitting and Lazy Loading โ€” React.lazy, Suspense, and Dynamic Imports

Day 22 of 40 โ€” React System Design Series