Skip to content

fix: hydration mismatch — defer showHeroCanvas matchMedia to useEffect#104

Merged
smaramwbc merged 1 commit into
mainfrom
fix-hydration-mismatch-hero-canvas
May 25, 2026
Merged

fix: hydration mismatch — defer showHeroCanvas matchMedia to useEffect#104
smaramwbc merged 1 commit into
mainfrom
fix-hydration-mismatch-hero-canvas

Conversation

@smaramwbc
Copy link
Copy Markdown
Owner

Summary

React error #418 surfaced in production after the navbar PR #103 landed. Cause: `HomePage.tsx`'s `showHeroCanvas` `useState` initialiser branched on `window.matchMedia` at first render — `false` on SSR, `true` on a desktop client. The conditional `{showHeroCanvas && }` then differed between the prerendered HTML and the hydrating client, and React blew up the hydration at that subtree.

Fix

Two-pass pattern: initial state is `false` on both SSR and the first client render, so hydration sees the same tree shape on both sides. After hydration the effect reads matchMedia and flips the flag — a normal post-mount re-render mounts HeroBackground on desktop.

Brief flash of "no background" on desktop is invisible because the canvas is decorative and the hero content paints from the prerendered HTML regardless.

Same pattern as the `ThemeProvider` fix in PR #95 (the SSR PR) — anything that reads `window` / `matchMedia` / `localStorage` in a useState initialiser during render is an SSR/client mismatch waiting to happen.

Why now

The bug was latent since PR #94 (lazy HeroBackground). React 19 was silently recovering and re-rendering the root before. PR #103's navbar `useLayoutEffect` + ResizeObserver path triggered extra hydration scrutiny that promoted the silent recovery to a visible error.

Test plan

  • `npm run build` — 6 routes prerender, no SSR fallback
  • `npx vitest run` — 284 + 2 skipped
  • `npm run lint`, `npm run typecheck` — green
  • Scanned all `src/pages` + `src/components` for similar useState-matchMedia patterns — none left
  • CI green
  • After deploy: open the homepage console and confirm no React #418 / hydration warnings

React error #418 surfaced in production after PR #103 shipped: the
HomePage's `showHeroCanvas` useState initialiser branched on
window.matchMedia at first render. On SSR it returned false (typeof
window === 'undefined' guard); on the client first render (during
hydration) it returned !matches — true on desktop. The conditional
{showHeroCanvas && <HeroBackground />} then differed between the
prerendered HTML and the hydrating client → React detected a tree
mismatch at the subtree, blew up the whole hydration, and re-rendered
the entire root from scratch on the client.

Two-pass pattern fix: initial state is `false` on both SSR and the
first client render, so hydration sees the same tree shape on both
sides. After hydration the effect reads matchMedia and flips the
flag — a normal post-mount re-render mounts HeroBackground on
desktop. Brief flash of "no background" is invisible because the
canvas is decorative and the hero content paints from the
prerendered HTML regardless.

Same pattern as the ThemeProvider fix in the SSR PR — anything that
reads window / matchMedia / localStorage in a useState initialiser
during render is an SSR/client mismatch waiting to happen. The
useState default must match what SSR produces; the real value
arrives in a mount effect.

Why this only surfaced now: the navbar PR #103 happened to expose it
because the navbar's ResizeObserver + useLayoutEffect path triggered
extra hydration scrutiny in React 19's dev / error-recovery flow.
The bug was latent since PR #94 (lazy HeroBackground); React was
silently recovering and re-rendering the root before, but it
counts as an error now.
@vercel
Copy link
Copy Markdown
Contributor

vercel Bot commented May 25, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
statewave-web Ready Ready Preview, Comment May 25, 2026 6:10pm

Request Review

@smaramwbc smaramwbc merged commit 9890da5 into main May 25, 2026
6 checks passed
@smaramwbc smaramwbc deleted the fix-hydration-mismatch-hero-canvas branch May 25, 2026 18:12
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