Skip to content

fix: hydration mismatch (React #418) in root HydrateFallback#9350

Open
tuha263 wants to merge 1 commit into
makeplane:previewfrom
tuha263:fix/hydrate-fallback-hydration-mismatch
Open

fix: hydration mismatch (React #418) in root HydrateFallback#9350
tuha263 wants to merge 1 commit into
makeplane:previewfrom
tuha263:fix/hydrate-fallback-hydration-mismatch

Conversation

@tuha263

@tuha263 tuha263 commented Jul 3, 2026

Copy link
Copy Markdown

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 prerenders HydrateFallback at build time, where typeof window === "undefined" → the guard returns an empty <div />. On the first client (hydration) render, however, next-themes resolves resolvedTheme synchronously from localStorage (its provider reads it in a useState initializer), 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 mounted flag (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. The resolvedTheme === undefined guard is preserved.

suppressHydrationWarning was 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

  • Bug fix (non-breaking change which fixes an issue)

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 at LogoSpinner inside HydrateFallback). After: clean console.

Test Scenarios

  • Production build (ssr: false prerender) 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.
  • Dev mode: the hydration warning at LogoSpinner/HydrateFallback is gone.
  • Spinner behaviour: still renders during app bootstrap once mounted (light/dark variants unaffected); on very fast hydration the fallback unmounts before painting the spinner — strictly less flash than before.
  • StrictMode double-invoked effect is idempotent.

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

  • Bug Fixes
    • Improved theme loading during hydration to reduce visual mismatches between server-rendered and client-rendered content.
    • Delayed rendering until the app is mounted and a theme is available, helping prevent flicker or inconsistent initial display.

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
Copilot AI review requested due to automatic review settings July 3, 2026 14:20
@CLAassistant

Copy link
Copy Markdown

CLA assistant check
Thank you for your submission! We really appreciate it. Like many open source projects, we ask that you sign our Contributor License Agreement before we can accept your contribution.
You have signed the CLA already but the status is still pending? Let us recheck it.

@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9af6faf3-3fed-4996-8ff7-38a7747cd715

📥 Commits

Reviewing files that changed from the base of the PR and between 7fbf14a and 9a2bb7f.

📒 Files selected for processing (1)
  • apps/web/app/root.tsx

📝 Walkthrough

Walkthrough

The HydrateFallback component in apps/web/app/root.tsx now imports useEffect and useState from React, replacing a typeof window === "undefined" check with a mounted state gated alongside resolvedTheme being defined, returning an empty <div /> until both conditions are met.

Changes

Hydration Fallback Fix

Layer / File(s) Summary
Mounted-state gating for HydrateFallback
apps/web/app/root.tsx
Adds useEffect/useState imports and replaces the typeof window === "undefined" check with a mounted state combined with resolvedTheme presence to gate the fallback render, updating related comments.

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)
Check name Status Explanation
Title check ✅ Passed The title clearly names the fix and the affected HydrateFallback root component.
Description check ✅ Passed The PR description covers the problem, fix, type of change, test scenarios, screenshots, and references.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 mounted flag in HydrateFallback and gated spinner rendering until after the first client mount.
  • Preserved the existing resolvedTheme === undefined guard so the spinner only renders once the theme is available.
  • Expanded inline comments to document the mismatch mechanism and why mounted resolves it.

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.

3 participants