Skip to content

fix(frontend): guard against SSR/hydration mismatch in theme provider - #1158

Open
omoh5 wants to merge 1 commit into
LabsCrypt:mainfrom
omoh5:fix/1083-theme-ssr-hydration-guard
Open

fix(frontend): guard against SSR/hydration mismatch in theme provider#1158
omoh5 wants to merge 1 commit into
LabsCrypt:mainfrom
omoh5:fix/1083-theme-ssr-hydration-guard

Conversation

@omoh5

@omoh5 omoh5 commented Jul 30, 2026

Copy link
Copy Markdown

Summary

Closes #1083

This PR adds a blocking inline script that reads the user's stored theme preference from localStorage and applies the dark class to <html> before the browser paints a single pixel. This prevents the flash-of-wrong-theme (FART) when the server renders a default theme class that differs from the user's persisted preference.

Problem

theme-provider.tsx wraps next-themes' ThemeProvider, which runs as a client component. In Next.js 16 + React 19, the script injected by next-themes via portal may not be truly blocking — it runs after React hydrates, causing:

  1. Flash of wrong theme: If the user previously chose light and the server renders dark, the browser paints dark first, then swaps to light.
  2. Hydration mismatch warning: React sees that the client DOM has a different dark class on <html> than what the server produced.

Solution

Two-part approach inside layout.tsx:

  • Blocking inline <script> in <head> — reads flowfi-theme from localStorage and synchronously adds/removes the dark class on <html> before the first paint. Handles dark, light, and system (via matchMedia) preferences. No flicker occurs because the correct class is present from the very first rendered frame.
  • suppressHydrationWarning on <html> (already present) — tells React to skip the hydration-difference check for this element because we intentionally mutate its class list before hydration.

Added comprehensive documentation:

  • theme-provider.tsx: Full JSDoc block explaining the SSR/hydration strategy and all configuration options.
  • layout.tsx: Inline comments explaining both the suppressHydrationWarning and the blocking script.

Files Changed

File Change
frontend/src/context/theme-provider.tsx Added comprehensive JSDoc documenting SSR/hydration strategy
frontend/src/app/layout.tsx Added blocking inline <script> in <head>; updated comments

Testing

  • TypeScript: tsc --noEmit — no new errors
  • Tests: 184 tests passing (20/21 suites), 1 pre-existing failure unrelated
  • Lint: no new warnings/errors

Manual Verification

To reproduce the original issue:

  1. Set theme to light in settings
  2. Hard-refresh the page
  3. Before fix: Observe a flash of dark theme before it switches to light
  4. After fix: The page renders in light theme from the first frame

…LabsCrypt#1083)

Add blocking inline script in <head> that reads localStorage theme preference before first paint, preventing flash-of-wrong-theme when client theme differs from server-rendered default. Add comprehensive JSDoc comments documenting the SSR/hydration strategy in both theme-provider.tsx and layout.tsx.
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.

[Frontend] theme-provider.tsx does not guard against SSR/hydration mismatch when localStorage theme differs from server-rendered class

1 participant