JavaScript

Next.js: SSR vs SSG vs ISR

Choosing the right rendering strategy for performance, SEO, and DX in Next.js.

July 10, 2025
4 min read
By useLines Team
Next.jsSSRSSGISRSEO
Illustration for Next.js: SSR vs SSG vs ISR

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 });
}

Related Posts