CSR vs SSR vs SSG vs ISR — Rendering Strategies Explained

12 min read

A deep dive into the four major web rendering strategies — Client-Side Rendering, Server-Side Rendering, Static Site Generation, and Incremental Static Regeneration. Learn the pros, cons, and when to use each.

Next.jsWeb PerformanceSSRSSGISRReact

Why Rendering Strategy Matters

Every time a user visits your web app, a critical decision has already been made: how and where your HTML is generated. This choice directly impacts page load speed, SEO rankings, server costs, and user experience. Modern frameworks like Next.js give you four powerful options — CSR, SSR, SSG, and ISR — and understanding the trade-offs is essential for building production-grade applications.

Overview of CSR, SSR, SSG, and ISR rendering flows

Overview of CSR, SSR, SSG, and ISR rendering flows

1. Client-Side Rendering (CSR)

With CSR, the server sends a minimal HTML shell with a JavaScript bundle. The browser downloads, parses, and executes the JS to render the entire UI. This is how traditional React (Create React App) and most SPAs work. The browser does all the heavy lifting.

Client-Side Rendering flow diagram showing blank screen until JS loads

Client-Side Rendering flow diagram showing blank screen until JS loads

typescript
// Classic CSR pattern — data fetched on the client
'use client';
import { useState, useEffect } from 'react';

export default function ProductsPage() {
  const [products, setProducts] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch('/api/products')
      .then(res => res.json())
      .then(data => {
        setProducts(data);
        setLoading(false);
      });
  }, []);

  if (loading) return <Skeleton />;
  return <ProductGrid products={products} />;
}
ProsCons
Rich, app-like interactivityPoor SEO — crawlers may not execute JS
Cheap hosting (static files + CDN)Slow First Contentful Paint (FCP)
Great for authenticated/private pagesBlank screen until JS loads (spinner UX)
Easy to deploy (no server needed)Larger JS bundles to download
Smooth client-side transitionsDependent on user's device performance

When to use CSR

Choose CSR for pages behind authentication (dashboards, admin panels, user settings), internal tools that don't need SEO, and highly interactive apps like editors or design tools where the content is user-specific.

2. Server-Side Rendering (SSR)

With SSR, the server generates the full HTML for every request. The browser receives a complete page it can display immediately, then hydrates it with JavaScript for interactivity. This is the default behavior for Server Components in Next.js App Router.

Server-Side Rendering flow diagram showing server processing per request

Server-Side Rendering flow diagram showing server processing per request

typescript
// SSR in Next.js App Router — Server Component (default)
// This runs on the server for every request
// No 'use client' directive = Server Component

interface Product {
  id: string;
  name: string;
  price: number;
}

export default async function ProductsPage() {
  // This fetch runs on the server, every request
  const res = await fetch('https://api.store.com/products', {
    cache: 'no-store', // Ensures fresh data every request
  });
  const products: Product[] = await res.json();

  return (
    <main>
      <h1>Products</h1>
      <ProductGrid products={products} />
    </main>
  );
}

// In Pages Router, you'd use getServerSideProps:
export async function getServerSideProps() {
  const res = await fetch('https://api.store.com/products');
  const products = await res.json();
  return { props: { products } };
}
ProsCons
Excellent SEO — full HTML for crawlersHigher server costs — renders every request
Always fresh data — never staleSlower TTFB (Time to First Byte)
Fast FCP — content visible immediatelyCannot be cached on a CDN easily
Works for dynamic, user-specific contentServer becomes a bottleneck under load
Smaller JS bundle than CSRHydration can cause brief non-interactive period

When to use SSR

Choose SSR when your page content is highly dynamic and must be fresh on every request (social feeds, real-time pricing, personalized content), when SEO is critical, and when the data cannot be stale even for a few seconds.

3. Static Site Generation (SSG)

SSG generates all HTML pages at build time. The pre-built HTML files are deployed to a CDN and served instantly to every user. There is no server computation at request time — the CDN simply returns the already-built page. This delivers the fastest possible load times.

typescript
// SSG in Next.js App Router — default for static data
// Pages with no dynamic data are automatically static

export default async function AboutPage() {
  // Static data = page is generated at build time
  const team = await fetch('https://api.company.com/team', {
    cache: 'force-cache', // default — cached at build
  });
  const members = await team.json();

  return (
    <main>
      <h1>Our Team</h1>
      <TeamGrid members={members} />
    </main>
  );
}

// For dynamic routes, generate all paths at build time:
export async function generateStaticParams() {
  const posts = await fetch('https://api.blog.com/posts').then(r => r.json());

  return posts.map((post: { slug: string }) => ({
    slug: post.slug,
  }));
}

// Pages Router equivalent:
export async function getStaticPaths() {
  const posts = await fetch('https://api.blog.com/posts').then(r => r.json());
  return {
    paths: posts.map((p: { slug: string }) => ({ params: { slug: p.slug } })),
    fallback: false,
  };
}
ProsCons
Blazing fast — served from CDN edgeContent is stale until next build
Perfect SEO — full HTML at build timeBuild times grow with number of pages
Cheapest to host — no server computeCannot show real-time or personalized data
Extremely reliable — no server failuresFull rebuild needed for content changes
Best Core Web Vitals scoresNot suitable for frequently changing content

When to use SSG

Choose SSG for content that rarely changes: marketing pages, documentation sites, blog posts, landing pages, and portfolio sites. If you can tolerate content being stale until your next deployment, SSG is the fastest and cheapest option.

4. Incremental Static Regeneration (ISR)

ISR is the best of both worlds — it combines SSG's speed with SSR's data freshness. Pages are pre-built at build time, served from the CDN, but automatically regenerated in the background after a specified revalidation interval. Users always get a fast cached response, and the cache is silently updated with fresh data.

Incremental Static Regeneration three-phase flow diagram

Incremental Static Regeneration three-phase flow diagram

typescript
// ISR in Next.js App Router — revalidate after 60 seconds
export const revalidate = 60; // seconds

export default async function BlogPage() {
  const posts = await fetch('https://api.blog.com/posts', {
    next: { revalidate: 60 }, // or set at fetch level
  });
  const data = await posts.json();

  return (
    <main>
      <h1>Latest Posts</h1>
      <PostList posts={data} />
    </main>
  );
}

// On-demand revalidation via API route:
// app/api/revalidate/route.ts
import { revalidatePath, revalidateTag } from 'next/cache';
import { NextRequest } from 'next/server';

export async function POST(request: NextRequest) {
  const { path, tag, secret } = await request.json();

  if (secret !== process.env.REVALIDATION_SECRET) {
    return Response.json({ error: 'Unauthorized' }, { status: 401 });
  }

  if (tag) revalidateTag(tag);
  if (path) revalidatePath(path);

  return Response.json({ revalidated: true, now: Date.now() });
}
ProsCons
SSG-level speed for cached pagesSlightly stale data (within revalidation window)
Great SEO — full HTML like SSGMore complex caching logic to reason about
No full rebuild for content updatesRequires a server/serverless runtime for regeneration
Scales to millions of pagesFirst request after revalidation serves stale page
On-demand revalidation for instant updatesNot truly real-time — use SSR if you need that

When to use ISR

Choose ISR for content that updates periodically but doesn't need to be real-time: e-commerce product pages, news articles, blog platforms, and content-heavy sites. It's ideal when you want CDN-level performance but your content changes more often than you can rebuild.

The Complete Comparison

AspectCSRSSRSSGISR
Rendering LocationBrowserServer (per request)Build server (once)Build + background
SEO❌ Poor✅ Excellent✅ Excellent✅ Excellent
First Contentful Paint🐢 Slow⚡ Fast⚡⚡ Fastest⚡⚡ Fastest
Time to Interactive🐢 Slow⚡ Medium⚡⚡ Fast⚡⚡ Fast
Data Freshness✅ Real-time✅ Real-time❌ Build-time only⚠️ Periodic
Server Cost💰 None💰💰💰 High💰 Minimal💰💰 Medium
Build Time⚡ Fast⚡ Fast🐢 Can be slow⚡ Fast (incremental)
Scalability⚡⚡ Excellent⚠️ Server-bound⚡⚡ Excellent (CDN)⚡⚡ Excellent (CDN)
ComplexityLowMediumLowMedium

Choosing the Right Strategy: A Decision Framework

Rather than picking one strategy for your entire app, modern frameworks let you mix and match per page. Here's a decision tree to help you choose the right approach for each route:

Decision tree for choosing between CSR, SSR, SSG, and ISR

Decision tree for choosing between CSR, SSR, SSG, and ISR

Hybrid Approach in Next.js App Router

The real power of Next.js is that you don't have to choose just one. You can use different strategies for different routes in the same application. Here's a practical example:

typescript
// app/page.tsx — SSG (static homepage)
export default function HomePage() {
  return <Hero />;
}

// app/blog/page.tsx — ISR (blog listing, revalidate every 5 min)
export const revalidate = 300;
export default async function BlogList() {
  const posts = await getPosts();
  return <PostGrid posts={posts} />;
}

// app/blog/[slug]/page.tsx — ISR with on-demand revalidation
export const revalidate = 3600; // 1 hour fallback
export default async function BlogPost({ params }: { params: { slug: string } }) {
  const post = await getPost(params.slug);
  return <Article post={post} />;
}

// app/dashboard/page.tsx — CSR (authenticated, no SEO needed)
'use client';
export default function Dashboard() {
  const { data } = useSWR('/api/analytics', fetcher);
  return <AnalyticsChart data={data} />;
}

// app/feed/page.tsx — SSR (real-time, personalized)
export const dynamic = 'force-dynamic'; // no caching
export default async function Feed() {
  const session = await getSession();
  const feed = await getPersonalizedFeed(session.userId);
  return <FeedList items={feed} />;
}

Key Takeaways

There is no single 'best' rendering strategy — only the right one for your specific use case. Start with SSG for everything you can, upgrade to ISR for content that changes periodically, use SSR for truly dynamic pages, and reserve CSR for interactive widgets behind authentication. The best applications use a hybrid approach, choosing the optimal strategy per route to balance performance, freshness, cost, and SEO.

CSR vs SSR vs SSG vs ISR — Rendering Strategies Explained