CSR vs SSR vs SSG vs ISR — Rendering Strategies Explained
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.
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
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
// 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} />;
}| Pros | Cons |
|---|---|
| Rich, app-like interactivity | Poor SEO — crawlers may not execute JS |
| Cheap hosting (static files + CDN) | Slow First Contentful Paint (FCP) |
| Great for authenticated/private pages | Blank screen until JS loads (spinner UX) |
| Easy to deploy (no server needed) | Larger JS bundles to download |
| Smooth client-side transitions | Dependent 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
// 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 } };
}| Pros | Cons |
|---|---|
| Excellent SEO — full HTML for crawlers | Higher server costs — renders every request |
| Always fresh data — never stale | Slower TTFB (Time to First Byte) |
| Fast FCP — content visible immediately | Cannot be cached on a CDN easily |
| Works for dynamic, user-specific content | Server becomes a bottleneck under load |
| Smaller JS bundle than CSR | Hydration 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.
// 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,
};
}| Pros | Cons |
|---|---|
| Blazing fast — served from CDN edge | Content is stale until next build |
| Perfect SEO — full HTML at build time | Build times grow with number of pages |
| Cheapest to host — no server compute | Cannot show real-time or personalized data |
| Extremely reliable — no server failures | Full rebuild needed for content changes |
| Best Core Web Vitals scores | Not 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
// 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() });
}| Pros | Cons |
|---|---|
| SSG-level speed for cached pages | Slightly stale data (within revalidation window) |
| Great SEO — full HTML like SSG | More complex caching logic to reason about |
| No full rebuild for content updates | Requires a server/serverless runtime for regeneration |
| Scales to millions of pages | First request after revalidation serves stale page |
| On-demand revalidation for instant updates | Not 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
| Aspect | CSR | SSR | SSG | ISR |
|---|---|---|---|---|
| Rendering Location | Browser | Server (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) |
| Complexity | Low | Medium | Low | Medium |
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
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:
// 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.