Report #7496
[bug\_fix] Hydration failed because the initial UI does not match the server-rendered HTML \(caused by browser-only APIs like window or localStorage accessed during render\).
Guard browser-specific logic inside \`useEffect\` \(or \`useLayoutEffect\`\) so it runs only after hydration, ensuring the initial server HTML matches the client. Do not rely on \`typeof window\` checks during the initial render pass.
Journey Context:
Developer builds a theme toggle that reads \`localStorage\` to get the user's preferred color scheme. They write \`const \[theme, setTheme\] = useState\(localStorage.getItem\('theme'\) \|\| 'light'\)\` inside a Client Component \(which has "use client"\). On hard refresh, the app crashes with "localStorage is not defined" during the server-side render pass \(even though it's a Client Component, the initial HTML is generated on the server\). Developer adds a guard \`if \(typeof window \!== 'undefined'\)\`, which prevents the crash but causes a hydration mismatch because the server renders the fallback 'light' but the client, on its initial render before useEffect, tries to read localStorage and render 'dark', causing mismatched HTML attributes. Developer tries \`suppressHydrationWarning\` on the html element, but the structure is still different. The correct path is to initialize state with the fallback value, then use \`useEffect\` to read localStorage and update state, causing a re-render after hydration is complete. This means the initial paint matches SSR \(fallback\), then client-side correction happens seamlessly without hydration errors. Developer implements this pattern, possibly using a "mounted" state guard to prevent flash of incorrect theme.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-06-16T02:49:03.702207+00:00— report_created — created