Verified against Claude Code · 2026-07-27
Find and fix a hydration mismatch instead of silencing the warning
Diagnoses a server-rendered React app where the client-rendered output disagrees with the server-rendered HTML, names the real cause from the actual common list, and fixes it at the source instead of reaching for suppressHydrationWarning.
The prompt
Ready to copy — highlighted parts are example details you can swap.
You are debugging a hydration mismatch in a server-rendered React application. The server-rendered HTML and the client's first render disagree, and your job is to find the actual source of the disagreement, not to make the warning go away by any means available.
COMPONENT
function LastUpdated({ timestamp }) { return <span>Updated {new Date(timestamp).toLocaleTimeString()}</span>; } — rendered inside a server-rendered page.
HYDRATION WARNING TEXT
Warning: Text content did not match. Server: "Updated 3:04:12 PM" Client: "Updated 3:04:15 PM"
SSR SETUP
A custom Node/Express server calling ReactDOMServer.renderToString, hydrated client-side with hydrateRoot; not a metaframework.
SUSPECTED CAUSE
Suspect it's the toLocaleTimeString() call producing a different exact second on the server versus the client, a few seconds apart.
DIAGNOSTIC RULES
Check, in order, the actual common causes of hydration mismatches, since the fix is different for each and guessing wrong wastes a debugging cycle: a value that differs between server and client because it depends on time (Date.now(), new Date() formatted without a fixed timezone) or randomness (Math.random(), a randomly-generated id) evaluated separately during the server render and the client's initial render; a check for a browser-only global such as window, document, or localStorage that is reachable on the client but throws or returns undefined on the server, causing genuinely different branches to render in each environment; invalid HTML nesting that the browser silently repairs when parsing the server-sent markup — a <div> nested inside a <p>, for instance — so the DOM the browser actually builds no longer matches what React's virtual tree expects to find when it hydrates; and third-party browser extensions or scripts that mutate the DOM after the server response arrives but before React hydrates it, which is a real cause outside your own code's control. For a value that is genuinely only knowable on the client — the actual viewport width, a value read from localStorage — the correct fix is to render a server-safe default on the initial render and update it inside a useEffect after mount, accepting that the visible UI will update once, deliberately, right after hydration; that is the standard pattern, not a hack. Reserve suppressHydrationWarning strictly for cases where the mismatch is provably harmless and unavoidable — a timestamp formatted as relative time ("2 minutes ago") is the textbook example — and never use it to silence a mismatch you have not first actually diagnosed, since it will hide a real bug just as readily as a harmless one.
OUTPUT FORMAT
1. The specific cause you identified, citing the exact line or pattern responsible, not a generic category.
2. The fix, as real code, and which of the standard patterns above it follows.
3. Why suppressHydrationWarning was or was not the right tool here, stated explicitly rather than left implicit in the fix.Customize
Optional — swap in your own details for the highlighted parts above.
Why this works
Walking the actual common-cause list in order, rather than jumping straight to a fix, matters because hydration mismatches have several genuinely distinct root causes that produce nearly identical symptoms — a mismatched text warning looks the same on the surface whether the cause is a timestamp, a browser-only API check, or malformed HTML the browser silently repaired — and a model that guesses the wrong category will confidently ship a fix, like wrapping the value in useEffect, that does nothing for a case actually caused by invalid nesting, leaving the real bug in place while looking resolved. The explicit two-step pattern for genuinely client-only values — render a server-safe default first, then update after mount in an effect — is the standard, documented fix specifically because it is honest about a real constraint: the server cannot know the client's viewport width or localStorage contents, so pretending otherwise by trying to guess or fake a value server-side just relocates the mismatch instead of resolving it, while a deliberate one-time post-hydration update is a known, accepted tradeoff. Restricting suppressHydrationWarning to provably harmless, unavoidable cases — and requiring that restriction be justified explicitly rather than applied by default — targets the actual failure mode this prompt exists to prevent: suppressHydrationWarning silences the exact signal that would otherwise reveal a real bug, so reaching for it before diagnosing the cause is functionally indistinguishable from hiding a defect rather than fixing one, and it will do exactly that for whichever future case doesn't happen to be the timestamp example everyone already knows about. Naming the specific SSR setup before diagnosing anything is also load-bearing, not throat-clearing: a custom Node server calling renderToString has different failure modes and different available fixes than a streaming renderToPipeableStream setup or a metaframework's own data-loading convention, since the point at which a server-only value becomes available, and whether partial hydration or streaming is even in play, changes which of the standard patterns actually applies here rather than being a generic hydration-mismatch fix copied from a different rendering setup entirely.
What you get back
Cause identified: new Date(timestamp).toLocaleTimeString() is called once during the server render and again during the client's initial render — a few seconds apart in real wall-clock time — producing genuinely different text on each side, exactly matching the warning's server/client second mismatch. function LastUpdated({ timestamp }) { const [display, setDisplay] = useState(() => formatStable(timestamp)); // server-safe, deterministic useEffect(() => { setDisplay(new Date(timestamp).toLocaleTimeString()); }, [timestamp]); return <span>Updated {display}</span>; } suppressHydrationWarning was not used here: the mismatch is fixable at the source (defer the wall-clock-dependent formatting to after mount) rather than provably unavoidable, so suppressing it would have hidden a real, fixable discrepancy instead of accepting a truly unavoidable one.
Verified against
Claude Code Sonnet 4.6 · 2026-07-27
ChatGPT GPT-5.1 · 2026-08-03
Changelog
- 2026-07-27 — Initial publish, verified against Claude Code (Sonnet 4.6) and ChatGPT (GPT-5.1) on React 19 SSR.
Need this built into your business?
If a prompt isn't enough — custom software, built and maintained for you — that's Scult's day job.
EXPLORE CUSTOM SOFTWARE
