“Explain SSR, SSG and ISR. When would you choose each?” is asked in almost every Next.js interview, often as the opener. Strong candidates go beyond the acronyms: they can say what happens to the first visitor after an ISR page expires, why a page they expected to be dynamic was built statically, and how they would render 80,000 product pages without a 40-minute build.
The question tests whether you can match a rendering strategy to the data: how often it changes, whether it differs per user, and how much stale data the business can tolerate. Those trade-offs matter more than the vocabulary.
Before you start
You should know that a server can produce HTML ahead of time or on demand, and what a CDN cache does. Basic App Router knowledge helps: app/ folders, page.tsx, async Server Components and the fetch API. Examples use Next.js 15.5; Pages Router equivalents are noted where interviewers still ask about them.
The short answer
SSG (static site generation) renders a page once at build time and serves the same HTML to everyone, ideal for docs and marketing pages. SSR (server-side rendering, called dynamic rendering in the App Router) renders on every request, needed when output depends on cookies, headers or must be up to the second. ISR (incremental static regeneration) serves a cached static page and regenerates it in the background after a revalidation window or on demand, which suits catalogues and blogs. In the App Router you do not pick an API: a route is static unless it reads request data or opts out of caching, and revalidate turns static into ISR.
How it works
At next build, Next tries to render every route. If the render completes without touching anything request-specific, the HTML and RSC payload are stored and served as files. If the render calls cookies(), headers(), connection(), reads searchParams, or makes a fetch with cache: 'no-store', Next stops and marks the route for rendering on demand.
The build prints the result. This table comes from a Next.js 15.5 test app:
Route (app) Size First Load JS Revalidate Expire
├ ○ /static 155 B 103 kB
├ ƒ /dyn-cookies 155 B 103 kB
├ ○ /fetch-default 155 B 103 kB
├ ƒ /fetch-nostore 155 B 103 kB
├ ○ /isr 155 B 103 kB 1m 1y
├ ● /posts/[slug] 155 B 103 kB
├ ├ /posts/a
├ └ /posts/b
○ (Static) prerendered as static content
● (SSG) prerendered as static HTML (uses generateStaticParams)
ƒ (Dynamic) server-rendered on demandThe code behind each strategy is small:
// Static (SSG): nothing request-specific
export default function About() {
return <p>Built at {new Date().toISOString()}</p>; // frozen at build time
}
// Dynamic (SSR): reads a cookie
import { cookies } from 'next/headers';
export default async function Account() {
const theme = (await cookies()).get('theme')?.value ?? 'light';
return <p>Theme: {theme}</p>;
}
// ISR: static, regenerated at most once a minute
export const revalidate = 60;
export default async function Pricing() {
const plans = await fetch('https://api.example.com/plans').then((r) => r.json());
return <PlanTable plans={plans} />;
}ISR follows the stale-while-revalidate pattern. When a request arrives after the window has expired, Next immediately returns the cached page, then regenerates it in the background. The next visitor gets the new page. If regeneration throws, the last good page keeps being served. The response headers show it: an ISR page sends Cache-Control: s-maxage=60, stale-while-revalidate=..., and a dynamic page sends private, no-cache, no-store, max-age=0, must-revalidate, so a CDN in front of next start caches only what it should.
In the Pages Router, the same strategies are explicit functions: getStaticProps for SSG, getStaticProps returning revalidate: 60 for ISR, getStaticPaths with fallback for dynamic paths, and getServerSideProps for SSR.
Step-by-step walkthrough
Take an online shop and choose a strategy for each page type.
Step 1: Classify pages by freshness and personalisation
Write two questions next to each route. Does the output differ per user? How stale may it be? The about page is the same for everyone and can be hours old: static. Product pages are the same for everyone and can be a few minutes old: ISR. The cart and account pages differ per user: dynamic. Search results depend on the query string: dynamic.
Step 2: Prerender the routes you know
For a dynamic segment such as app/products/[slug], generateStaticParams lists which slugs to build ahead of time.
// app/products/[slug]/page.tsx
export const revalidate = 300; // ISR: at most five minutes stale
export const dynamicParams = true; // unknown slugs render on first request
export async function generateStaticParams() {
const top = await fetch('https://api.example.com/products?top=1000').then((r) => r.json());
return top.map((p: { slug: string }) => ({ slug: p.slug }));
}With dynamicParams = true, a slug that was not prerendered is rendered when first requested and then cached like the others. With false, unknown slugs return 404. That mirrors fallback: 'blocking' and fallback: false in the Pages Router.
Step 3: Add on-demand revalidation where time is not enough
A five-minute window means a price change can take up to five minutes to appear, plus one request to trigger it. When the CMS or admin panel knows exactly when data changes, call revalidatePath('/products/' + slug) or revalidateTag('product-' + slug) from a Server Action or a webhook Route Handler. Keep the time window as a safety net in case a webhook is lost.
Step 4: Keep dynamic pages small
The account page must be dynamic, but its layout, navigation and help text are not personal. Keep the dynamic work in the page and stream slow parts behind Suspense boundaries, so time to first byte stays low. For one small personal detail on an otherwise static page, such as a “Hi, Asha” greeting, render it in a Client Component that fetches after load rather than making the whole page dynamic.
Worked scenario
A retailer’s product pages used generateStaticParams to return all 80,000 products. Builds took 40 minutes, a failed product API call in the middle of a build broke the entire deploy, and hotfixes waited behind the build queue. Meanwhile, stock levels were baked into the static HTML with revalidate = 3600, so customers could add items that had sold out an hour earlier.
Two changes fixed it. First, generateStaticParams now returns only the 1,000 best sellers, with dynamicParams = true. Everything else is rendered on its first request and cached, so builds take a few minutes and the long tail is still served statically after one visit. Second, the page separates slow-changing content from fast-changing content. Name, images and description stay in the ISR page with tag-based revalidation from the product admin. Stock and delivery estimates come from a small Client Component that calls an uncached endpoint after load, so they are always current without making the whole page dynamic.
Common mistake
- “ISR regenerates the page every 60 seconds.” Nothing runs on a timer. Regeneration is triggered by a request after the window expires.
- “The first visitor after expiry sees fresh data.” They see the stale page; the regenerated one goes to later visitors.
- “A page with
fetchis SSR.” In Next.js 15 a plainfetchin an otherwise static route runs at build time and its result is frozen. - Testing ISR in
next dev. Development renders every request. Usenext buildandnext start. - Ignoring the lowest window. If a layout sets
revalidate = 3600and the page fetches withrevalidate: 60, the route revalidates on the shorter interval. - Putting personal data in a static page. One user’s data would be cached and shown to everyone.
Verify the behavior
Build, start in production mode, and watch the x-nextjs-cache header on a page with export const revalidate = 5.
npx next build && npx next start -p 3000 &
curl -si localhost:3000/isr5 | grep -iE 'x-nextjs-cache|isr5'
# x-nextjs-cache: STALE window expired: old page returned, regeneration starts
# isr5 2026-10-06T14:32:39.372Z
curl -si localhost:3000/isr5 | grep -iE 'x-nextjs-cache|isr5'
# x-nextjs-cache: HIT the regenerated page
# isr5 2026-10-06T14:32:45.523ZThe first response is the old page marked STALE, and the second has a new timestamp marked HIT. That pair is the clearest proof of stale-while-revalidate you can show in an interview.
Follow-up questions
What is partial prerendering? An experimental feature (experimental.ppr, available only on canary releases of Next.js 15, and the default model behind Cache Components in Next.js 16) that serves a static shell instantly and streams dynamic holes inside Suspense boundaries in the same response, so one route can be partly static and partly dynamic.
Where is ISR output stored when self-hosting? On the server’s disk and in memory by default. With several instances you need a shared cache handler, or each instance regenerates and serves its own copy.
How does export const dynamic = 'force-static' differ from the default? It forces static rendering even if the code reads request data, which then returns empty values. Use it carefully, mainly for Route Handlers.
Is client-side rendering still useful? Yes, for private, highly interactive widgets where SEO does not matter, such as a dashboard chart that polls for updates.
Interview exercise
Design rendering for a news site: the home page lists the latest stories, article pages rarely change after publishing but corrections happen, and a “saved articles” page lists the reader’s bookmarks. Breaking news must appear on the home page within a minute.
Answer and reasoning
The home page is the same for every reader and must be at most a minute old, so make it ISR with revalidate = 60 and also call revalidatePath('/') when an editor publishes, so breaking news appears immediately while the window covers missed events. Article pages are ideal for ISR with a long window, such as a day, plus revalidateTag from the CMS when a correction is published; generateStaticParams can prerender recent articles and let older ones render on first request. The saved articles page differs per reader and depends on the session cookie, so it must be dynamic, and its layout can still stream instantly. This answer shows the decision rule interviewers want: shared data gets cached with a deliberate staleness budget, personal data is rendered per request.
Continue learning
Practise more rendering questions in the Next.js interview questions and the Next.js MCQs. Continue with Next.js caching and revalidation for the cache layers behind ISR, and streaming with loading.tsx for fast dynamic pages. The official guide to Incremental Static Regeneration covers configuration details.