SSG, SSR, ISR & PPR
Next.js supports several rendering strategies, chosen per route to balance speed and freshness.
Next.js is not tied to one rendering model. Each route can use whichever strategy fits its data:
| Strategy | When it renders | Best for |
|---|---|---|
| Static (SSG) | At build time, reused | Marketing, docs |
| Dynamic (SSR) | On every request | Personalized, live data |
| ISR | Cached, rebuilt on a timer | Blogs, catalogs |
| PPR | Static shell + dynamic holes | Mixed pages |
Partial Prerendering (PPR) is the newest: it serves a static shell instantly and streams in the dynamic parts, giving you the best of both.
Example
// Static by default (no dynamic APIs, cached fetches)
// -> SSG
export default async function Page() {
const data = await fetch('https://api.example.com/docs', {
cache: 'force-cache',
}).then((r) => r.json());
return <article>{data.title}</article>;
}When to use it
- A marketing landing page uses SSG to pre-render at build time so every visitor gets HTML from the CDN edge instantly.
- A personalised dashboard uses SSR to render fresh, user-specific data on every request without a client-side fetch.
- An e-commerce product page uses ISR with a 5-minute revalidation so inventory changes propagate quickly without rebuilding the whole site.
More examples
Static generation (SSG)
cache: 'force-cache' (the default) causes Next.js to render this page once at build time and serve the result statically.
// app/about/page.tsx — statically generated at build time
export default async function AboutPage() {
const content = await fetch('https://cms.example.com/about', {
cache: 'force-cache',
}).then(r => r.json());
return <section>{content.body}</section>;
}Server-side rendering (SSR)
Reading cookies() automatically opts this route into dynamic SSR so data is always personalised and fresh.
// app/orders/page.tsx — rendered on every request
import { cookies } from 'next/headers';
export default async function OrdersPage() {
const cookieStore = await cookies();
const token = cookieStore.get('auth-token')?.value;
const orders = await fetchOrders(token);
return <ul>{orders.map((o: any) => <li key={o.id}>{o.total}</li>)}</ul>;
}ISR with revalidate
Exporting revalidate makes Next.js serve the cached page and regenerate it in the background after the interval.
// app/products/page.tsx — ISR: rebuild every 5 minutes
export const revalidate = 300;
export default async function ProductsPage() {
const products = await fetch('https://api.example.com/products').then(r => r.json());
return <ul>{products.map((p: any) => <li key={p.id}>{p.name}</li>)}</ul>;
}
Discussion