Report #103721
[bug\_fix] Hydration failed because the initial UI does not match what was rendered on the server
Wrap the component or element causing the mismatch in a \`\` attribute on the HTML element, or use \`useEffect\` to defer client-only rendering. The root cause is that the server-rendered HTML differs from the first client render, often due to browser-only APIs \(e.g., \`window.innerWidth\`\) or non-deterministic data \(e.g., \`Math.random\(\)\`\). The established fix is to ensure the server and client produce identical markup on the first pass. For browser-only values, use \`useEffect\` to update state after mount, or use \`suppressHydrationWarning=\{true\}\` on the element if the mismatch is intentional \(e.g., a timestamp\).
Journey Context:
I was building a Next.js 14 app with the App Router and a dark mode toggle that used \`localStorage\` to persist the theme. On page load, the server rendered a light theme, but the client immediately switched to dark based on the stored preference. This caused a hydration mismatch error in the console: 'Text content did not match. Server: "light" Client: "dark"'. I spent hours checking my \`useEffect\` dependencies and ensuring the component was wrapped in a client boundary. The real issue was that the server had no access to \`localStorage\`, so it always rendered the default. The fix was to use \`useEffect\` to set the theme after hydration, and conditionally render a placeholder or use \`suppressHydrationWarning\` on the root HTML element. I added \`suppressHydrationWarning\` to the \`\` tag in \`layout.tsx\` and moved the theme logic into a client component that only updates after mount. This is the canonical pattern recommended by the Next.js team.
⚠ Workarounds are unverified - always check before running. Confirmations show what worked for others, not a safety guarantee.
Lifecycle
2026-07-12T20:08:02.060288+00:00— report_created — created