Hydration fails or the page flickers after SSR
“The console reports that server-rendered HTML did not match the client, or production logs show React error #418, and parts of the page re-render, flicker, or lose event handlers.”
ERROR CODES YOU MAY SEE
A code is a clue. Use its meaning and surrounding evidence to narrow the cause.
418React minified errors · libraryHydration failed because the server-rendered HTML did not match the first client render; React regenerates that tree on the client.
Production builds show only the number. Reproduce in a development build to see the diff, then look for time, locale, randomness, browser-only branches, invalid HTML nesting, or HTML rewritten by extensions or a CDN.
Official reference for 418 (opens in new tab)Code definition reviewed
THE PRINCIPLE
Hydration is not a re-render; it adopts existing HTML. Anything that depends on the clock, locale, time zone, randomness, or the browser belongs after hydration or must be passed in from the server.
FIRST MOVES
- Reproduce in a development build to get the diff; production only reports the error number.
- Look for Date.now(), new Date(), toLocaleString(), Math.random(), typeof window branches, and localStorage or matchMedia reads in render.
- Replace hand-rolled id counters with useId, and keep the component tree identical on both sides.
- Validate the HTML: no <div> or <p> inside <p>, no nested <a> or <button>; the browser parser rewrites invalid nesting.
- Rule out extensions, iOS format detection, and CDN rewriting by testing in a clean profile against the raw origin response.
- Defer genuinely client-only UI with useEffect, next/dynamic with ssr: false, or Astro client:only; use suppressHydrationWarning only for a single unavoidable text node.
TOOLS: THEN → NOW
PATTERN SNAPSHOT
// Server and first client render agree: fixed locale and time zone.
const fmt = new Intl.DateTimeFormat("en-US", { timeZone: "UTC", dateStyle: "medium" });
function LocalDate({ iso }: { iso: string }) {
const [text, setText] = useState(() => fmt.format(new Date(iso)));
// Switch to the viewer's locale only after hydration.
useEffect(() => { setText(new Date(iso).toLocaleDateString()); }, [iso]);
return <time dateTime={iso}>{text}</time>;
}
const id = useId(); // stable across server and client, unlike a counterCLOSE THE AI. EXPLAIN THIS.
Why does an incrementing id counter break hydration even when the server and client run the same code?RELATED SYMPTOMS
SOURCES
Guide reviewed