SSR vs SSG vs ISR in Next.js
Answer
Next.js supports multiple rendering strategies: Server-Side Rendering (SSR), Static Site Generation (SSG), and Incremental Static Regeneration (ISR).
Rendering Timeline
Comparison
| Aspect | SSG | SSR | ISR |
|---|---|---|---|
| When generated | Build time | Each request | Build + background |
| Performance | Fastest | Slower | Fast |
| Data freshness | Stale | Always fresh | Configurable |
| Use case | Blogs, docs | User dashboards | E-commerce |
| CDN cacheable | Yes | No | Yes |
SSG - Static Site Generation
// PAGES ROUTER
export async function getStaticProps() {
const posts = await fetchPosts();
return {
props: { posts },
};
}
export default function Blog({ posts }) {
return posts.map((post) => <Post key={post.id} post={post} />);
}
// APP ROUTER (default behavior)
async function BlogPage() {
const posts = await fetch("/api/posts", { cache: "force-cache" });
return posts.map((post) => <Post key={post.id} post={post} />);
}
SSR - Server-Side Rendering
// PAGES ROUTER
export async function getServerSideProps(context) {
const user = await getUser(context.req.cookies.token);
return {
props: { user },
};
}
export default function Dashboard({ user }) {
return <div>Welcome, {user.name}</div>;
}
// APP ROUTER
async function DashboardPage() {
const user = await fetch("/api/user", {
cache: "no-store", // SSR - always fresh
});
return <div>Welcome, {user.name}</div>;
}
ISR - Incremental Static Regeneration
// PAGES ROUTER
export async function getStaticProps() {
const products = await fetchProducts();
return {
props: { products },
revalidate: 60, // Regenerate every 60 seconds
};
}
// APP ROUTER
async function ProductsPage() {
const products = await fetch("/api/products", {
next: { revalidate: 60 }, // Revalidate every 60 seconds
});
return <ProductList products={products} />;
}
On-Demand Revalidation
// API route to trigger revalidation
// pages/api/revalidate.js (Pages Router)
export default async function handler(req, res) {
await res.revalidate("/products");
return res.json({ revalidated: true });
}
// app/api/revalidate/route.js (App Router)
import { revalidatePath, revalidateTag } from "next/cache";
export async function POST(request) {
revalidatePath("/products");
// or revalidateTag('products');
return Response.json({ revalidated: true });
}
Decision Flowchart
Best Practices
// Mix strategies in one app
// app/page.js - SSG (home page)
// app/blog/page.js - ISR (content updates periodically)
// app/dashboard/page.js - SSR (user-specific)
// Dynamic segments with SSG
// pages/blog/[slug].js
export async function getStaticPaths() {
const posts = await getPosts();
return {
paths: posts.map((post) => ({ params: { slug: post.slug } })),
fallback: "blocking", // or true, or false
};
}
export async function getStaticProps({ params }) {
const post = await getPost(params.slug);
return { props: { post }, revalidate: 3600 };
}
Use Cases
| Strategy | Best For |
|---|---|
| SSG | Marketing pages, blogs, documentation |
| SSR | User dashboards, personalized content, real-time data |
| ISR | Product pages, news sites, frequently updated content |
Key Points
- SSG: Pre-built at build time, cached on CDN
- SSR: Fresh content on every request
- ISR: Static with background regeneration
- App Router uses fetch cache options
- Pages Router uses getStaticProps/getServerSideProps
- Use on-demand revalidation for immediate updates
- Choose based on data freshness requirements