Next.js rendering, made simple
Pick based on freshness, speed, and personalization:
SSG (Static)
- Build at deploy; serve HTML from CDN
- Fastest, cheapest, most scalable
- Use for: blogs, docs, marketing
ISR (Static + refresh)
- Like SSG, but auto‑regenerates on a timer
- Stays fast, gets fresh in the background
- Use for: product pages, news, catalogs
SSR (Server)
- HTML rendered per request on server
- Fresh and personalized, but slower and costlier
- Use for: dashboards, auth‑heavy pages
Quick guide
- Rarely changes → SSG
- Changes sometimes → ISR (set revalidate)
- Real‑time or per‑user → SSR
Default to SSG, add ISR where needed, reserve SSR for truly dynamic pages.
Consider three axes:
- Freshness window (seconds to days)
- Personalization (per-user vs global)
- Build frequency (per deploy vs on demand)
Match the rendering mode to these needs rather than picking one by habit.
Example use cases
- Product details: ISR with
revalidate: 60
and on-demand revalidation on update events. - News homepage: ISR with short TTL (15–60s) + edge caching.
- Admin dashboard: SSR with user session; cache fragments downstream where safe.
- Docs/marketing: SSG with link prefetching.
Edge and middleware
Place simple personalization and A/B tests at the edge without forcing SSR of whole pages. Use edge middleware for redirects, locale detection, and cookie-based variants.
Pitfalls and remedies
- Over-SSR: servers under load; move stable blocks to static partials and hydrate islands.
- ISR stampedes: protect revalidation endpoints; use queues and soft TTLs.
- Cache invalidation: wire on-demand revalidation to CMS/webhooks and include surrogate keys.
Headers that matter
Set Cache-Control
wisely:
Cache-Control: public, s-maxage=3600, stale-while-revalidate=60
For user-specific SSR responses use private, no-store
and cache only CDN fragments when safe.
Measuring success
Track LCP and TTFB across routes. Preload critical assets and enable prefetching of likely next routes to reduce perceived latency.
Conclusion
Choose SSR, SSG, or ISR per page based on data needs. Use edge and caching to get the best of both worlds, and automate revalidation so content stays fresh without sacrificing speed.
Implementation snippets
SSR example (getServerSideProps)
export async function getServerSideProps() {
const data = await fetch("https://api.example.com/data", {
cache: "no-store",
}).then((r) => r.json());
return { props: { data } };
}
SSG example (getStaticProps)
export async function getStaticProps() {
const posts = await getAllPosts();
return { props: { posts } };
}
ISR example (Next 13+ route segment)
export const revalidate = 60; // seconds
export default async function Page() {
const res = await fetch("https://api.example.com/top-products", {
next: { revalidate },
});
const data = await res.json();
return <Products data={data} />;
}
On-demand revalidation (API route)
export default async function handler(req, res) {
if (req.query.secret !== process.env.REVALIDATE_TOKEN) {
return res.status(401).json({ message: "Invalid token" });
}
await res.revalidate("/products");
return res.json({ revalidated: true });
}