Skip to content

Fix theme hydration mismatch - #58

Merged
lexler merged 1 commit into
mainfrom
fix-theme-hydration-mismatch
Sep 9, 2026
Merged

Fix theme hydration mismatch#58
lexler merged 1 commit into
mainfrom
fix-theme-hydration-mismatch

Conversation

@lexler

@lexler lexler commented Sep 9, 2026

Copy link
Copy Markdown
Owner

Problem

React reported error 418 (text hydration mismatch) on every page. useTheme initialised its state from localStorage and matchMedia during the first client render, so the server-rendered ☀️ could differ from the client's 🌙.

Fix

The pre-hydration ThemeScript already puts the light/dark class on the html element before React runs. The hook now treats that class as the single source of truth via useSyncExternalStore: it renders light on the server, reads the real class once hydrated, and follows later class changes through a MutationObserver. Toggling writes the class and localStorage directly.

Theme resolution logic (stored preference, then system preference) now lives only in ThemeScript.

Verification

  • Unit tests rewritten around the document class as the source of truth, including a server-render test for the hydration contract
  • Full suite: 341 tests pass, lint clean
  • Built and served locally with theme=dark stored: no console errors on load, moon icon shown, toggle works both ways and persists

🤖 Generated with Claude Code

The hook initialised state from localStorage and matchMedia during the
first client render, so the server-rendered sun icon could differ from
the client's moon icon and React reported a hydration error on every
page. The pre-hydration script already puts the theme class on the html
element, so the hook now treats that class as the single source of truth
through useSyncExternalStore, rendering light on the server and reading
the real theme once hydrated.
@lexler
lexler merged commit 202c51c into main Sep 9, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant