Careeroza
SSG SSR and ISR Compared
Side by Side
| SSG | ISR | SSR | |
|---|---|---|---|
| HTML built | At build | At build + in background after N s | Per request |
| Data freshness | Build time | At most N seconds old (or on-demand) | Always fresh |
| TTFB | Best (CDN) | Best (CDN) | Depends on data |
| Server cost | Lowest | Low | Highest |
| Personalised content | No | No | Yes |
| App Router trigger | Default for routes without dynamic APIs | revalidate | Dynamic APIs / no-store / force-dynamic |
Pick Per Route
Example
A rule of thumb as code. Ask: does it depend on the user? how often does the data change?
type Route = { path: string; perUser: boolean; changesEveryMin: number | null };
function strategy(r: Route): string {
if (r.perUser) return 'SSR (dynamic)';
if (r.changesEveryMin === null) return 'SSG (static)';
return 'ISR (revalidate = ' + r.changesEveryMin * 60 + 's)';
}
const routes: Route[] = [
{ path: '/about', perUser: false, changesEveryMin: null },
{ path: '/blog/[slug]', perUser: false, changesEveryMin: null },
{ path: '/products/[id]', perUser: false, changesEveryMin: 60 },
{ path: '/deals', perUser: false, changesEveryMin: 5 },
{ path: '/cart', perUser: true, changesEveryMin: 0 },
{ path: '/account/orders', perUser: true, changesEveryMin: 0 },
];
for (const r of routes) console.log(r.path.padEnd(16), strategy(r));Output
/about SSG (static) /blog/[slug] SSG (static) /products/[id] ISR (revalidate = 3600s) /deals ISR (revalidate = 300s) /cart SSR (dynamic) /account/orders SSR (dynamic)