How would you pick SSG, ISR, SSR, or Edge for personalization?
JAMstack Developer
answer
Use a pyramid: SSG for evergreen pages, ISR for frequently updated content with background revalidation, SSR for highly dynamic routes, and Edge Functions to segment users and personalize via cached variants. Avoid flicker by rendering critical personalization at the edge (headers, nav, hero) using signed cookies or tokens, and keep the content body static. Control caches with surrogate keys, versioned assets, stale-while-revalidate, and user-segment cache keys; test with synthetic probes per region.
Long Answer
A robust JAMstack architecture embraces static-first delivery while selectively layering dynamism. The decision matrix weighs content volatility, personalization scope, time-to-first-byte, and operational cost. Combine SSG for speed, ISR for freshness, SSR for high-temporal pages, and Edge Functions for instant segmentation near users, all held together by disciplined caching and anti-flicker techniques.
1) Decision framework
Think in four axes: volatility (how often content changes), personalization breadth (anonymous, segment, individual), acceptable staleness window, and rendering cost.
- SSG: near-zero volatility, fully cacheable.
- ISR: moderate volatility; allow minutes of staleness with background rebuilds.
- SSR: high volatility or per-request data with strict freshness.
- Edge Functions: low-latency decisions per request, ideal for segmentation and lightweight personalization.
2) When to use SSG
Pre-render landing pages, static product content, blog posts, docs, and policy pages. Bake navigation and category taxonomies into the build. Pair with long cache lifetimes, immutable assets, and surrogate-key invalidation for emergency purges. SSG maximizes throughput and minimizes origin load.
3) When to use ISR
Adopt ISR for content that changes minutes to hourly: category pages, article hubs, homepages with promo slots. Configure a revalidation window (for example, five to ten minutes) and trigger on-demand rebuilds via webhooks when editors publish. ISR serves the last good page, then refreshes in the background, preserving user latency while achieving freshness.
4) When to use SSR
Reserve SSR for routes that require exact-now data: dashboards, inventory-critical details, pricing that changes per request, or heavy A/B logic. Limit SSR surface to the minimum viable HTML shell and stream the rest. Guard with timeouts and fallback templates, and cache SSR responses by tenant or segment where safe.
5) Edge Functions for personalization without server drag
Keep the body static; personalize the frame. At the edge, read a signed cookie or token to determine segment (for example, locale, plan, returning user). Modify response headers or swap small fragments (nav labels, call-to-action, hero banner) before the page leaves the CDN. Use small edge templates or JSON snippets; avoid edge-side database calls. Cache by segment key so variants stay fast and isolated.
6) Avoiding flicker and hydration mismatches
Flicker occurs when client-side code overwrites static HTML after load. Prevent it by:
- Rendering critical personalized fragments at the edge so HTML and client agree
- Using deterministic initial state embedded in HTML (a config script tag) that matches client hydration.
- Deferring non-critical personalization until idle and animating in with placeholders that match final dimensions to avoid layout shifts.
- Keeping the same markup shape across variants; change content, not structure.
7) CDN cache design: keys, invalidation, and consistency
Design cache keys explicitly: {path}:{lang}:{segment}:{abBucket}. Add Vary on the exact headers you use for segmentation. Use surrogate keys to purge related pages (for example, invalidate all pages tagged with a category key). Prefer stale-while-revalidate to mask origin latency, and stale-if-error to ride through brief outages. Version assets and data endpoints so you can roll forward safely.
8) Data access patterns
For SSG/ISR, read from a content store at build time or on-demand regeneration. For SSR, call a fast data layer (edge-friendly key-value or cached GraphQL). For edge personalization, prefetch small lookup tables to a global key-value store or embed variant maps in the deployment artifact to avoid cold fetches.
9) Search, lists, and pagination
Lists with frequent updates fit ISR plus client-side incremental fetching. Use keyset pagination to avoid expensive offsets. Pre-render the first page; load subsequent pages on client with conditional requests. For personalized recommendations, render a generic layout statically, slot personalized items via client fetch with skeletons that prevent layout shift.
10) Editorial workflows and revalidation
Wire the CMS to trigger on-demand ISR revalidation when content changes. For batch updates (for example, price changes), publish an invalidation list of surrogate keys to purge within seconds. Keep a “hotlist” of high-traffic pages with shorter ISR windows; cold content gets longer windows.
11) Reliability and cost control
Cap SSR concurrency, set budgets for edge CPU time, and precompute reusable fragments. Spread build workloads using partial rebuilds and incremental cache; avoid full-site rebuilds for minor updates. Add synthetic monitors that request each major variant per region to detect stale caches or mis-keyed personalization.
12) Observability
Emit headers that show cache status (hit, miss, stale), segment, and revalidation age. Log edge decisions, ISR regeneration results, and SSR timings. Track Core Web Vitals and measure layout shift around personalized areas. Set alerts on ISR error rates, edge timeouts, and variant cache fragmentation.
This layered approach—static where possible, incremental where helpful, dynamic where required, and personalized at the edge—delivers a fast, fresh, and flicker-free JAMstack architecture at global scale.
Table
Common Mistakes
- Using SSR everywhere “just in case,” causing higher cost and tail latency.
- Personalizing entirely on the client, leading to flicker and layout shift.
- Segmenting by broad headers (like user-agent) without strict Vary, creating cache pollution.
- Over-fragmenting cache keys (too many variants) so hit ratio collapses.
- Treating ISR as instant; not setting a realistic staleness window or webhook revalidation.
- Doing heavy data fetches in Edge Functions and turning the edge into a slow origin.
- Forgetting deterministic initial state, causing hydration mismatches.
- Relying only on full-site rebuilds; no surrogate-key purge or on-demand revalidation.
- Lacking regional synthetic probes, so stale or mismatched variants go unnoticed.
Sample Answers (Junior / Mid / Senior)
Junior:
“I would use SSG for static pages and ISR for pages that update during the day. For personalization, I would use Edge Functions to select a variant from a cookie and avoid flicker by rendering the header at the edge. I would keep the body static and use stale-while-revalidate on the CDN.”
Mid:
“My JAMstack architecture applies SSG for evergreen routes, ISR with webhooks for editorial updates, and SSR only for real-time routes. Personalization is computed at the edge using a signed cookie; cache keys include path, language, and segment. I avoid hydration mismatches by embedding initial config in HTML. Surrogate keys allow purging related pages instantly.”
Senior:
“I design a static-first site: SSG and ISR handle most content; SSR is restricted to strict-freshness routes and streams partial HTML. Edge Functions compute lightweight personalization (nav, hero) using signed tokens and segmented cache keys to eliminate flicker. Surrogate keys and on-demand ISR keep content fresh. Observability exposes cache status, ISR regeneration, and edge timings per region.”
Evaluation Criteria
A strong answer selects SSG for evergreen content, ISR for frequent updates with background rebuilds, SSR for strict-freshness routes, and Edge Functions for low-latency personalization of small fragments. It explains anti-flicker tactics: render critical personalized fragments at the edge, embed deterministic initial state, and avoid structural changes during hydration. It defines cache keys with path, language, and segment, uses surrogate-key invalidation and stale-while-revalidate, and limits edge work to lightweight decisions. Red flags: SSR everywhere, client-only personalization, missing Vary, no revalidation hooks, and hydration mismatches.
Preparation Tips
- Classify routes by volatility and personalization needs; map each to SSG, ISR, SSR, or Edge.
- Implement signed segment cookies and edge logic to swap nav or hero variants.
- Add surrogate keys to pages and wire CMS webhooks to on-demand ISR revalidation.
- Embed a small config script with the initial user segment to keep hydration consistent.
- Pre-render first pages of lists; use keyset pagination and client fetch for subsequent pages.
- Add cache status headers and logs; build dashboards for hit ratio, ISR success rates, and edge latency.
- Run synthetic probes per region and per segment to detect stale or mismatched caches.
- Cap SSR concurrency and stream responses; prefer incremental adoption over blanket SSR.
- Drill rollback: purge by surrogate key, extend ISR windows, and fall back to generic variants if a segment misbehaves.
Real-world Context
A publisher serving multiple languages pre-rendered articles with SSG, moved hubs and the homepage to ISR with five-minute windows, and reserved SSR for live scores. Personalization at the edge set locale and subscription segment via a signed cookie, swapping the header and hero without hitting origin. Cache keys included {path}:{lang}:{segment}, and surrogate-key purges cleared entire topic clusters on updates. A small script embedded the initial segment so the client hydrated without flicker. With regional probes, the team caught a variant cache miss in one region and fixed the Vary rule. The result: fast global pages, fresh promos within minutes, and seamless personalized chrome with no layout shift.
Key Takeaways
- Use SSG for evergreen, ISR for frequent updates, SSR for strict freshness, and Edge Functions for lightweight personalization.
- Eliminate flicker by rendering critical personalized fragments at the edge and embedding deterministic initial state.
- Design explicit cache keys with path, language, and segment; apply surrogate-key invalidation and stale-while-revalidate.
- Limit edge logic to fast lookups; keep heavy data fetching off the edge.
- Monitor cache status, ISR regeneration, and edge timings across regions.
Practice Exercise
Scenario:
You are building a content-heavy site with multilingual support and subscription-based personalization. Editors publish dozens of updates per hour; subscribers see different nav and hero promos. Global traffic peaks require consistent performance.
Tasks:
- Classify routes into SSG, ISR, and SSR. Propose revalidation windows for ISR and justify which routes must remain SSR.
- Design Edge Functions to read a signed segment cookie and choose one of three variants (anonymous, trial, paid). Identify which fragments are personalized at the edge and which remain static.
- Define cache keys: {path}:{lang}:{segment}:{abBucket} and Vary rules. Show how you would prevent variant cache pollution.
- Wire CMS webhooks to on-demand ISR revalidation; describe fallback if the CMS is down.
- Implement anti-flicker: embed a config script with the initial segment and ensure markup parity with the client.
- Plan invalidation using surrogate keys for topic clusters; demonstrate a purge that updates all affected pages in seconds.
- Add observability: cache status headers, ISR regeneration logs, edge timing metrics, and regional synthetic probes.
- Establish guardrails: SSR concurrency caps, edge CPU budgets, and emergency switches to generic variants.
- Provide a rollback plan for a bad personalization release: purge variant keys, disable edge swap, and extend ISR windows temporarily.
Deliverable:
A concise architecture and runbook showing how to combine SSG, ISR, SSR, and Edge Functions for a content-heavy, personalized JAMstack architecture without cache-stale or flicker.

