fix: hydration mismatch (React #418) in root HydrateFallback#9350
fix: hydration mismatch (React #418) in root HydrateFallback#9350tuha263 wants to merge 1 commit into
Conversation
The SPA build prerenders HydrateFallback where `typeof window` is undefined, emitting an empty <div />. On the first client render next-themes resolves the theme synchronously from localStorage, so the existing guard falls through and renders the spinner subtree — the hydration tree no longer matches the prerendered HTML, and React logs error makeplane#418 (hydration mismatch) on every page load, discarding the prerendered shell and re-rendering it client-side. Gate the spinner behind a mounted flag so the hydration pass renders the same empty <div /> as the prerender. The spinner still appears immediately after mount; behaviour is otherwise unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LDA1JjtePo7qPyE3CiQcVv
|
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthroughThe ChangesHydration Fallback Fix
Estimated code review effort: 1 (Trivial) | ~5 minutes Related PRs: None mentioned in the provided data. Suggested labels: bug, frontend Suggested reviewers: None mentioned in the provided data. 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Pull request overview
Fixes a repeatable React hydration mismatch in the SPA (ssr: false) prerendered shell by ensuring HydrateFallback renders the same minimal markup on the prerender and on the initial client hydration pass, then shows the spinner immediately after mount.
Changes:
- Added a
mountedflag inHydrateFallbackand gated spinner rendering until after the first client mount. - Preserved the existing
resolvedTheme === undefinedguard so the spinner only renders once the theme is available. - Expanded inline comments to document the mismatch mechanism and why
mountedresolves it.
Description
Every page load of the web app logs
Minified React error #418(hydration mismatch) once, and React discards the prerendered shell and re-renders it client-side.Root cause: with
ssr: false(SPA mode), react-router prerendersHydrateFallbackat build time, wheretypeof window === "undefined"→ the guard returns an empty<div />. On the first client (hydration) render, however,next-themesresolvesresolvedThemesynchronously from localStorage (its provider reads it in auseStateinitializer), so the guard falls through and renders the full spinner subtree. Server HTML ≠ first client render → React #418 by construction, on every page.Fix: gate the spinner behind a
mountedflag (the canonical next-themes SSR/SPA pattern). The hydration pass now renders the same empty<div />as the prerender; the spinner appears immediately after mount. TheresolvedTheme === undefinedguard is preserved.suppressHydrationWarningwas considered and rejected: it only silences attribute/text warnings on a single element — it neither covers a structurally different subtree nor prevents the client-side re-render.Type of Change
Screenshots and Media (if applicable)
Console before: one
Minified React error #418; visit https://reactjs.org/docs/error-decoder.html?invariant=418…per page load (dev mode:Warning: Expected server HTML to contain a matching <div> in <div>pointing atLogoSpinnerinsideHydrateFallback). After: clean console.Test Scenarios
ssr: falseprerender) served and loaded on multiple routes (workspace home, project issues): React feat: issue filter views #418 no longer fires; zero console errors; pages boot normally.LogoSpinner/HydrateFallbackis gone.References
None — happy to link an issue if you'd like one filed.
🤖 Generated with Claude Code
https://claude.ai/code/session_01LDA1JjtePo7qPyE3CiQcVv
Summary by CodeRabbit