Skip to content

ISR, SSG, SSR, and Edge: Picking the Right Rendering Mode for Each Page

Static for marketing pages. ISR for blog. SSR for dashboards. Edge for low-latency reads. Most teams pick wrong.

John Cravey with AIFounder5 min readUpdated Jul 6, 2026

The Next.js App Router supports four rendering modes per route: fully static (SSG), incremental static regeneration (ISR), server-side rendering on every request (SSR), and edge runtime. Most teams pick one and use it everywhere. That’s the wrong move — different routes have different freshness, traffic, and personalization needs, and the rendering choice should match.

Free estimate · 2 minutes

Slow pages lose rankings and leads. Price the fix.

Build the estimate to see what a fast rebuild would look like for your site and what we would ship in the first thirty days.

What each mode actually does

  1. SSG: HTML is generated at build time and served as a static file from the CDN. Fastest possible delivery, zero per-request server cost, but the content is frozen until the next build.
  2. ISR: SSG plus a `revalidate` window. The page is served from the static cache until it’s N seconds old, then the next request triggers a background regeneration. Stale-while-revalidate semantics — fast and reasonably fresh.
  3. SSR: HTML is generated on every request. Always fresh, always pays a server round-trip per visit, scales with your traffic.
  4. Edge: SSR but the function runs at a Cloudflare/Vercel edge node near the user instead of in a central region. Lower latency, fewer features (no Node APIs, smaller package size limit).

Default: SSG for marketing, ISR for blog, SSR for authenticated routes

That’s the FH default and it covers 80% of cases without thinking. Your homepage, your services pages, your locations pages — all SSG. They change rarely, traffic loves the CDN-hit, and the build cost is paid once. Your blog index and post pages — ISR with a 60-minute or 24-hour `revalidate`, so new posts and edits propagate without a full deploy. Anything behind auth (admin, dashboard, account) — SSR, because the content is per-user and shouldn’t be cached at the edge.

How to set each mode

Route-segment config in the file itself. Export `revalidate` (number of seconds) for ISR, `dynamic = 'force-static'` for SSG, `dynamic = 'force-dynamic'` for SSR, `runtime = 'edge'` for edge.

// app/page.tsx — SSG (default behavior with no data fetches)
export const dynamic = "force-static";

// app/blog/page.tsx — ISR, 1 hour
export const revalidate = 3600;

// app/admin/page.tsx — SSR
export const dynamic = "force-dynamic";

// app/api/proxy/route.ts — Edge
export const runtime = "edge";

When ISR earns its keep

ISR is the sweet spot for content that changes occasionally but reads constantly. Blog posts are the canonical case — once published, they’re read thousands of times but maybe edited once a month. A 24-hour `revalidate` window means a single regeneration per day per page, and every other request hits the cache. We use ISR on every FH client blog.

It also works for category pages, listing pages, and any “data view” where the source updates a few times a day. Pair ISR with `revalidatePath()` or `revalidateTag()` in server actions, and you can trigger a regeneration on demand when content changes — best of both worlds.

When SSR is actually necessary

Three signals: (1) the content is per-user (a dashboard, an account page); (2) the content depends on request-time data like a cookie or auth header; (3) the content needs to be fresh down to the second (a stock ticker, a sports score). Anything else is over-rendering — you’re paying per-request server cost for content that could be cached.

Edge runtime: low-latency reads, but fewer features

Edge functions run on Cloudflare’s network and start in single-digit milliseconds. Latency from a remote user is a fraction of what a centralized Node function would be. The trade-off: no Node-only APIs (no `fs`, no `child_process`, no most-of-`node:` modules), smaller bundle size limit, fewer NPM packages work. We use edge for routes that are read-heavy and don’t touch the database directly — geolocation-based redirects, A/B test variant selection, Turnstile token verification.

Mixed-mode in the same app

Different routes can have different modes. The same Next.js app can serve a SSG homepage, ISR blog posts, SSR admin dashboard, and an edge API route. Next handles the routing and the build correctly. You don’t need separate deploys or separate hostnames.

Common mistakes we see in audits

  • Marketing pages set to SSR by default because someone enabled `dynamic = 'force-dynamic'` at the layout level. Every page now hits the server on every visit. Bandwidth bill goes up, page-load time goes up.
  • Blog post pages set to SSG with no ISR, so new posts don’t appear until the next deploy. Editorial team has to redeploy to publish. Friction kills cadence.
  • Admin pages set to SSG with auth, so the static page caches one user’s session and serves it to everyone. Real production incident we’ve cleaned up twice.
  • Edge runtime set on a route that imports a Node-only library, so the build silently falls back to Node and you don’t get the latency win.

Picking per-route in 30 seconds

  1. Is the content per-user? → SSR.
  2. Does it need to be fresh down to the second? → SSR.
  3. Does it change a few times a day or week? → ISR.
  4. Does it change with each deploy? → SSG.
  5. Is it a read-heavy proxy with no DB? → Edge.

Measuring the impact

Set up a simple TTFB monitor — we use Cloudflare’s built-in analytics on every FH client site — and compare TTFB across rendering modes on the same site. An SSG page should serve in under 80ms TTFB from the CDN. An ISR cache-hit should be the same. An ISR cache-miss should be under 600ms. SSR should be under 800ms. Anything above those is a signal something’s wrong — usually a misconfigured cache or an unexpected database call on a route that should be static.

How this maps to FH client work

Across the client book, every site has the same shape: SSG for marketing pages, ISR for blog content, SSR for any authenticated area. The split isn’t exotic — it’s the boring default that performs well, scales cheaply, and stays maintainable. If your site is rendering the same mode for everything, book an audit — we can usually identify three or four routes that should change mode in the first hour.

Answers

Frequently asked questions

What does each rendering mode actually do?

Static generation renders at build time and serves a file. Incremental regeneration serves that file and refreshes it on a schedule. Server rendering builds the page per request. Edge runs a limited runtime close to the user. The differences are freshness, cost, and what APIs are available.

What are the sensible defaults?

Static for marketing pages, incremental regeneration for a blog, server rendering for authenticated routes. That covers most of a business site correctly. Deviating is fine when a page genuinely needs something else, but starting from these three avoids most of the mistakes audits find.

When does incremental regeneration earn its keep?

When content changes on a cadence rather than per request: blog posts, catalogues, listings. It gives static-file performance with a freshness window you choose. If the content changes on every request it is the wrong tool, and if it never changes it is unnecessary machinery.

When is server rendering actually necessary?

When the response genuinely depends on the request: a logged-in user, personalized data, or something that must be correct at this instant. Anything else rendered per request is paying the cost of freshness it does not need, on every visit, forever.

What is the trade-off with the edge runtime?

Lower latency for reads at the cost of a smaller API surface. Node built-ins and many libraries are unavailable, so code that runs fine on the server can fail there. It suits small, latency-sensitive reads and suits complex application logic badly.

Can one app mix rendering modes?

Yes, and it should. Modes are per route, which is the entire point: the marketing pages are static, the blog regenerates, the dashboard renders per request. Picking one mode for an entire application is what produces either stale marketing pages or an expensive dashboard.

What mistakes show up most often in audits?

Marketing pages rendered per request because a shared layout reads a cookie. Blogs rebuilt entirely for one post's change. Dashboards statically generated and mysteriously stale. And edge chosen for a route whose dependencies do not run there, discovered in production.

How do I pick a mode quickly?

Two questions. Does the response depend on who is asking? If yes, server render. If no, does the content change on a schedule? If yes, incremental; if no, static. That decision takes about thirty seconds per route and gets it right the large majority of the time.

How do I know a page is rendering as intended?

Check the build output for what was pre-rendered and check response headers on the live page for cache behaviour. Assuming from the code is unreliable, because a single dynamic call anywhere in the tree can opt a whole route out of static generation silently.

What forces a page to become dynamic accidentally?

Reading headers or cookies, using a dynamic function in a shared layout, or an uncached fetch in a component the page includes. The route does not warn you; it just stops being static, and the cost shows up as server load rather than as an error.

Does incremental regeneration risk serving stale content?

By design, for the length of the window you set. That is usually fine for a blog and unacceptable for pricing or availability. Choose the window from how wrong the page is allowed to be, not from how often the source changes.

How does the rendering mode affect hosting cost?

Substantially. Static pages are files served cheaply; per-request rendering is compute on every visit. A marketing site that accidentally renders dynamically costs meaningfully more to run and is slower, and the fix is usually removing one dynamic call from a layout.

Question we did not answer? Ask us directly and we will answer it here.

John Cravey, Founder
Written by
John Cravey
Founder

Founder of Frontend Horizon. Writes most of the long-form work on the FH blog.

Newer post
GA4 Attribution Models: Which One to Look At When
Older post
Google Ads for Small Businesses: The Complete Guide to Running Campaigns That Don’t Waste Your Budget
Keep reading

More from the blog

Next.js·10 min

Speed Is a Ranking Factor: The Next.js Performance Checklist by Business Size

Google measures your speed with real users and ranks you on it. Next.js hands you the tools to win. Most sites leave them switched off.

Next.js·6 min

Next.js 16.1 in Production: The Migration Playbook We Run on Every FH Site

Next 16.1 is the lean target. Here’s the exact migration we run, what breaks, and what to delete after.

Next.js·4 min

Server Components vs Client Components: The Mental Model That Stops You Reaching for ‘use client’

Most teams add ‘use client’ because they’re scared. The bundle pays for it.