fix(studio): avoid shell theme hydration mismatch on SSR#106
Conversation
useResolvedShellTheme initialized state by reading window.localStorage and window.matchMedia, so SSR emitted data-mdcms-theme="light" while the first client render resolved to the user's actual preference, triggering a React hydration mismatch on the studio shell root. Start with a stable "light" value on both server and first client render and defer resolution to useEffect so the real theme is applied post-mount.
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughSwitches theme initialization to a constant Changes
Sequence Diagram(s)sequenceDiagram
participant Server as Server (SSR)
participant HTML as Initial HTML
participant Browser as Browser (pre-hydration)
participant Shell as StudioShellFrame
participant Effect as useIsomorphicLayoutEffect
Server->>HTML: render initial markup + inline SHELL_THEME_INLINE_SCRIPT
HTML->>Browser: deliver HTML
Browser->>Shell: mount StudioShellFrame (suppressHydrationWarning)
Shell->>Effect: run isomorphic layout effect (early)
Effect->>Browser: read localStorage (`STUDIO_THEME_STORAGE_KEY`) and matchMedia
Effect->>Shell: setAttribute("data-mdcms-theme", resolvedTheme) / avoid no-op updates
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
…dration Initializing useResolvedShellTheme to a stable "light" on SSR avoided the hydration mismatch but left dark-mode users with a brief light-theme flash until useEffect ran post-mount. Inject an inline script as the first child of the shell root that reads the stored preference and system media query synchronously during HTML parsing, mutating data-mdcms-theme before the browser paints. suppressHydrationWarning lets React tolerate the pre-hydration mutation, and the hook now uses an isomorphic layout effect so client-only mounts also resolve the theme before paint instead of after. Adds a test asserting the inline script is emitted in SSR markup.
Summary
Two-part fix for the studio shell theme hydration.
1. Hydration mismatch (first commit).
useResolvedShellThemereadwindow.localStorage/window.matchMediainside itsuseStateinitializer. SSR produceddata-mdcms-theme="light"while the first client render produced"dark", so React logged a hydration mismatch on the shell root and refused to patch the attribute. The hook now initializes to a stable"light"so SSR and first client render agree.2. Dark-theme flash (second commit). Fix #1 alone leaves dark-mode users looking at light styles until
useEffectruns post-paint. We now:<script>as the first child of the shell root that reads the stored preference and system media query synchronously during HTML parsing and mutatesdata-mdcms-themebefore the browser paints (the same patternnext-themesuses for<html>, scoped to the shell).suppressHydrationWarningon the root so React tolerates the pre-hydration DOM mutation.useLayoutEffecton client,useEffectfallback for SSR) so client-only mounts — e.g., SPA navigation into Studio — also resolve the theme before paint rather than after.No regression for explicit
shellThemeprop callers (tests assertingdata-mdcms-theme="light"/"dark"still pass).Test plan
bun test --cwd packages/studio ./src/lib/studio.test.ts(38 pass, including new inline-script assertion)bunx nx typecheck studioprefers-color-scheme: darkand no stored preference: confirm no hydration error and no white flash on initial load"light"preference while system is dark: confirm light theme renders without flickerSummary by CodeRabbit
Release Notes
Bug Fixes
Refactor
New Features
Tests