Why this bug keeps repeating
Teams hit this error because localStorage is a browser API, but App Router code can still be rendered on the server first. The mismatch appears when the initial server output assumes one value and the client immediately swaps to another after reading localStorage.
The visible error message talks about hydration. The deeper problem is that the component never had a stable first-render contract.
The wrong fixes people try first
Mounted flags and client-only wrappers can sometimes hide the symptom, but they often widen the client-rendered surface and make the component harder to reason about. That tradeoff is only worth it when the boundary truly belongs on the client.
- Do not read localStorage directly in server-rendered output paths.
- Do not suppress hydration warnings as the main fix.
- Do not turn a whole layout client-side because one preference value lives in the browser.
The cleaner repair path
Choose a stable server default, render that first, then layer the browser preference after hydration in the smallest client island that needs it. This keeps the initial tree honest and minimizes client-only spread.
If the preference is important enough to influence the first render, consider persisting it somewhere the server can read safely instead of making localStorage carry too much responsibility.
What to test after the fix
Cold-load the page in production mode, clear storage, and then reload with existing stored values. Those two paths often reveal whether the first render and the hydrated state actually agree.
If the fix still relies on several guard rails to stay quiet, the boundary is probably still wrong even if the warning stopped.