Crux Read real React/Next snippets — a hydration-mismatch source, an SSR data-fetch race, an ISR revalidate config, and a Suspense placement — and pick the highest-leverage fix.
Your altitude — climbing toward senior
ZeroJuniorMiddleSenior
You are at senior altitude — in orbit
◷ 14 min
Render-strategy bugs are read in code and config, not in the docs. Read each snippet, predict what it does at runtime, and choose the fix a senior engineer would make first.
Goal
Practise the loop every render-strategy incident runs: read the component or config, predict the hydration, freshness, or streaming behaviour, and reach for the structural fix — not a patch over the symptom.
Snippet 1 — the greeting that flickers
function Greeting() { // read during render, on BOTH server and client const hour = new Date().getHours(); const msg = hour < 12 ? 'Good morning' : 'Good evening'; return <h1>{msg}</h1>;}
Quiz
Completed
This component throws 'Text content did not match' on hydration and spikes CLS. Where is the bug and what is the correct fix?
Heads-up The element type is irrelevant — the diff is the text content, driven by reading a live value during render. Changing the tag does nothing.
Heads-up getHours() is fine; UTC would only mask the timezone half of the problem. The moment-of-render difference remains, so it still mismatches. The cure is not reading time during render at all.
Heads-up Suspense streams async data; it does not make a synchronous Date read deterministic. The two renders still disagree. Defer the value to an effect instead.
The page is server-rendered, but this component always paints 'Loading…' in the initial HTML and the real name appears only after hydration. Why, and what is the higher-leverage approach?
Heads-up The URL is fine — the data does arrive, just after hydration. The issue is that useEffect cannot run during SSR, so the server HTML can never include the fetched name.
Heads-up That only changes the placeholder text; the server still cannot fetch in an effect. The real fix is fetching on the server so the name is in the initial HTML.
Heads-up 'use server' marks Server Actions, not a way to run useEffect on the server — effects never run during SSR. Fetch in a Server Component or loader and pass the result down.
Prices change unpredictably, sometimes minutes apart, but `revalidate = 3600` means a price can be stale for up to an hour. How do you keep ISR's static-delivery win while bounding staleness to seconds?
Heads-up revalidate = 1 hammers regeneration on every request window and still serves stale data between price changes you cannot predict. On-demand revalidation refreshes exactly when the price actually changes.
Heads-up SSR pays server-compute on every request for content identical across users, losing ISR's CDN-cache win. On-demand revalidation keeps static delivery and bounds staleness to seconds.
Heads-up revalidate = 0 opts the route out of caching entirely — effectively SSR — discarding the static-delivery benefit you are trying to keep.
Snippet 4 — the Suspense placement
async function Dashboard() { return ( <Shell> <Stats /> {/* fast: ~10 ms */} <Suspense fallback={<Spinner/>}> <OrderHistory /> {/* slow: ~400 ms query */} </Suspense> </Shell> );}
Quiz
Completed
With streaming SSR (renderToPipeableStream), what does this Suspense placement achieve, and what would happen if the boundary were removed?
Heads-up With renderToPipeableStream, Suspense boundaries are the unit of streaming on the server: the shell flushes immediately and each boundary streams as its data resolves.
Heads-up Boundary placement does not reorder rendering; Stats is fast and flushes with the shell. The boundary lets the slow part stream later without blocking the fast part.
Heads-up That is what happens WITHOUT the boundary. The boundary does the opposite — it isolates the slow query so the rest streams immediately.
Recap
Four reading patterns cover most render-strategy incidents: a live value (new Date(), Math.random(), window) read during render is a hydration mismatch — defer it to useEffect; useEffect never runs during SSR, so data must be fetched on the server and passed down (or embedded in a dehydrated cache) to appear in the initial HTML; time-based ISR bounds staleness loosely, but on-demand revalidation via webhook tightens it to seconds while keeping CDN delivery; and a Suspense boundary around a slow query is what lets streaming SSR flush the shell first instead of blocking on the slowest part. Diagnose from the code and config, fix the structure, then verify TTFB, INP, and the mismatch count.