
SSR, CSR, SSG, and ISR in Next.js - What They Are and When to Use Each One
Next.js gives you four ways to render pages and each solves a different problem. This guide explains all four with real code examples and a practical decision guide.
If you've started learning Next.js, you've probably seen these four terms thrown around a lot. SSR, CSR, SSG, ISR. Tutorials mention them, documentation references them, and if you're coming from plain React, they can feel overwhelming at first.
But here's the thing - once you understand what problem each one solves, everything clicks. They're not four completely different technologies. They're four different answers to the same question: when and where should your page be rendered?
Let's build that understanding properly.
Why Does Rendering Strategy Even Matter?
Before getting into each type, you need to understand why this is even a decision you have to make.
When a user visits your website, their browser needs to show them a page. That page is made of HTML. The question is - where does that HTML come from and when is it created?
Option 1: The browser builds it (using JavaScript) Option 2: The server builds it before sending it to the browser Option 3: It was already built before anyone even asked for it
Each of these options has trade-offs around speed, SEO, and how fresh your data is. That's what SSR, CSR, SSG, and ISR are about - they're just different strategies for answering that question.
Next.js is unique because it lets you use all four strategies in the same project. Different pages can use different rendering methods depending on what they need.
CSR - Client-Side Rendering
This is how plain React works by default, and Next.js supports it too.
How it works:
The server sends a nearly empty HTML file to the browser. It looks something like this:
<!DOCTYPE html>
<html>
<head><title>My App</title></head>
<body>
<div id="root"></div>
<script src="/bundle.js"></script>
</body>
</html>
The browser downloads the JavaScript bundle, runs it, fetches data from your API, and then builds the page. All of this happens in the browser, on the user's device.
In Next.js, you opt into CSR using the "use client" directive and fetching data inside a useEffect:
"use client";
import { useState, useEffect } from "react";
export default function Dashboard() {
const [data, setData] = useState(null);
useEffect(() => {
fetch("/api/dashboard-stats")
.then(res => res.json())
.then(data => setData(data));
}, []);
if (!data) return <p>Loading...</p>;
return <div>{data.totalUsers} users</div>;
}
Where CSR works well:
Private dashboards where SEO doesn't matter
Pages that need real-time data (live charts, notifications)
Anything behind a login screen
Highly interactive pages where the user is constantly doing things
Where CSR falls short:
Public pages that need to rank on Google - search engines often struggle with pages that render entirely in the browser
Slow devices or poor internet connections - the user has to wait for JS to download and run before seeing anything
First load performance - the page feels slow because there's a blank screen while everything loads
SSR - Server-Side Rendering
With SSR, the HTML is generated on the server for every single request. When a user asks for a page, the server fetches the data, builds the HTML, and sends the complete page to the browser.
How it works step by step:
User requests
/products/42Next.js server runs your component on the server
Fetches the product data from your database or API
Builds the full HTML with that data already inside it
Sends the complete HTML to the browser
The browser displays the page immediately - no waiting for JS to fetch data
In Next.js App Router, SSR is straightforward - just fetch data directly inside your component:
// This is a Server Component - runs on the server for every request
export default async function ProductPage({ params }) {
const product = await fetch(
`https://api.example.com/products/${params.id}`,
{ cache: "no-store" } // no-store means always fetch fresh data
).then(res => res.json());
return (
<div>
<h1>{product.name}</h1>
<p>{product.description}</p>
<span>Rs. {product.price}</span>
</div>
);
}
The cache: "no-store" tells Next.js to always go to the server for fresh data - never use a cached version. This is what makes it true SSR.
Where SSR works well:
Pages where data changes frequently and needs to be fresh on every request
Pages that are personalized per user (profile pages, feed pages)
Public pages where SEO matters and data can't be pre-built
Where SSR falls short:
High traffic pages - every request hits your server, which puts load on your infrastructure
Slower response times compared to static pages - there's always server processing before the page goes out
If your database or external API is slow, every page load feels slow too
SSG - Static Site Generation
SSG is the opposite end of the spectrum from SSR. Instead of generating pages on every request, pages are generated once at build time and stored as static HTML files.
When a user visits your page, the server just sends the pre-built HTML file. No database queries. No server processing. Just a file being delivered - which is incredibly fast.
How it works:
When you run next build, Next.js generates HTML files for all your static pages. These files sit on the server (or a CDN) and get delivered instantly to every user.
In Next.js App Router, a component is statically generated by default if you don't do anything special:
// This page is statically generated at build time
export default async function AboutPage() {
const teamData = await fetch("https://api.example.com/team").then(res => res.json());
return (
<div>
<h1>Our Team</h1>
{teamData.members.map(member => (
<div key={member.id}>
<h2>{member.name}</h2>
<p>{member.role}</p>
</div>
))}
</div>
);
}
For dynamic routes (like /blog/[slug]), you tell Next.js which pages to generate at build time using generateStaticParams:
// Generate these blog posts at build time
export async function generateStaticParams() {
const posts = await fetch("https://api.example.com/posts").then(res => res.json());
return posts.map(post => ({
slug: post.slug,
}));
}
export default async function BlogPost({ params }) {
const post = await fetch(
`https://api.example.com/posts/${params.slug}`
).then(res => res.json());
return (
<article>
<h1>{post.title}</h1>
<p>{post.content}</p>
</article>
);
}
Next.js will generate a separate HTML file for every slug returned by generateStaticParams.
Where SSG works well:
Blogs and articles - content doesn't change after publishing
Documentation sites
Marketing and landing pages
Product pages where content updates are infrequent
Where SSG falls short:
Pages with data that changes frequently - if your prices update every hour, a statically built page will show outdated prices until the next build
Personalized pages - you can't build a different version of the page for every user at build time
Large sites with thousands of dynamic pages - build times become very long
ISR - Incremental Static Regeneration
ISR is Next.js's most interesting rendering strategy and it's one of the main reasons developers choose Next.js over other frameworks.
It solves the main problem with SSG: once a page is built, it goes stale. With ISR, pages are still statically generated (so they're fast), but Next.js can regenerate them in the background after a certain amount of time.
How it works:
Page is built at deploy time and served as static HTML (same as SSG)
You set a revalidation time - say, 60 seconds
After 60 seconds, the next request to that page triggers a background rebuild
The current user still sees the old page instantly (no waiting)
The next user after the rebuild gets the fresh page
The user experience is always fast. The data just gets refreshed periodically behind the scenes.
In Next.js App Router, you enable ISR by adding a revalidate option to your fetch:
export default async function ProductsPage() {
// Regenerate this page at most once every 10 minutes
const products = await fetch("https://api.example.com/products", {
next: { revalidate: 600 } // 600 seconds = 10 minutes
}).then(res => res.json());
return (
<div>
<h1>All Products</h1>
{products.map(product => (
<div key={product.id}>
<h2>{product.name}</h2>
<p>Rs. {product.price}</p>
</div>
))}
</div>
);
}
You can also set the revalidation time at the page level:
// Revalidate this entire page every 5 minutes
export const revalidate = 300;
export default async function NewsPage() {
const articles = await fetch("https://api.example.com/news").then(res => res.json());
return (
<div>
{articles.map(article => (
<h2 key={article.id}>{article.title}</h2>
))}
</div>
);
}
On-demand revalidation is another option - instead of time-based regeneration, you trigger a rebuild manually when your data actually changes. If you update a blog post in your CMS, the CMS sends a webhook to your Next.js app, which then regenerates that specific page immediately.
// app/api/revalidate/route.ts
import { revalidatePath } from "next/cache";
export async function POST(request) {
const { slug } = await request.json();
revalidatePath(`/blog/${slug}`);
return Response.json({ revalidated: true });
}
Where ISR works well:
E-commerce product pages - prices and stock can update periodically without a full rebuild
News or blog sites where content updates several times a day
Any page where data is mostly stable but not completely static
Large sites where rebuilding everything on every content change isn't practical
Where ISR falls short:
Pages that need real-time data - ISR has a delay. If you need live stock prices or live scores, ISR isn't the right tool
Highly personalized pages - same problem as SSG, you can't customize per user
Comparing All Four Side by Side
CSR | SSR | SSG | ISR | |
|---|---|---|---|---|
When is HTML built? | In the browser | On server per request | At build time | At build time + background updates |
First load speed | Slow (blank screen first) | Fast | Fastest | Fastest |
SEO | Poor | Good | Best | Best |
Data freshness | Always fresh | Always fresh | Can go stale | Mostly fresh |
Server load | Low | High | Very low | Very low |
Good for | Dashboards, private pages | Personalized public pages | Blogs, docs, marketing | E-commerce, news, content sites |
How to Choose the Right One
Here's a practical way to think about it when you're deciding for a specific page:
Is the page behind a login? Does SEO not matter? Go with CSR. Keep it simple.
Is the data highly personalized per user (like a social media feed or inbox)? Go with SSR. Build it fresh on every request.
Does the content rarely change and SEO matters? Go with SSG. Build it once and serve it fast.
Does the content change occasionally but not per-request, and SEO matters? Go with ISR. Get the speed of static with data that stays reasonably fresh.
The good news about Next.js is that you're not making one choice for your entire app. Your homepage can be SSG, your product pages can be ISR, your user dashboard can be CSR, and your profile page can be SSR - all in the same project. That flexibility is what makes Next.js genuinely powerful for production apps.
One Thing Worth Remembering
A lot of developers new to Next.js try to figure out the "best" rendering strategy overall. There isn't one. SSR isn't better than SSG - they solve different problems.
The right question to ask for each page is: how often does this data change, and who is it for? Answer that honestly and the right strategy usually becomes obvious.
Comments
Sign in to join the conversation
Related Articles
More insights you might enjoy

React vs Next.js: What's Actually Different and When to Use Which
React and Next.js are often confused - one is a UI library, the other is a full framework built on top of it. This post breaks down the real differences: rendering strategies, routing, API routes, and when to pick one over the other.

HTTP Status Codes Explained - What They Actually Mean and Why Developers Should Know Them
Most developers know 200 and 404. But understanding the full range - and when to use them in your own APIs - makes you a noticeably better backend developer.