src/context/ThemeContext.tsx#ThemeProvider initializes React state unconditionally:
const [theme, setTheme] = useState<Theme>("light");
The actual applied theme (dark or light) is decided synchronously before hydration by
the inline themeInitScript in layout.tsx, which adds the dark class straight to
<html>. ThemeProvider's useEffect then reads that class back to correct its own
theme state:
useEffect(() => {
setTheme(document.documentElement.classList.contains("dark") ? "dark" : "light");
...
}, []);
Between first paint and this effect running, theme is "light" regardless of the
visitor's actual preference. src/components/ui/ThemeToggle.tsx renders its icon
directly from this state:
{theme === "dark" ? <Sun className="h-4 w-4" /> : <Moon className="h-4 w-4" />}
For a visitor whose page is actually rendering in dark mode (via a stored preference or
prefers-color-scheme: dark), the toggle button shows a Moon icon (meaning "click to
go dark") for one render/paint even though the page is already dark — the icon is
momentarily backwards relative to the real state, a small but visible flash on every
page load in dark mode.
Suggested fix: read the initial theme synchronously via a lazy useState initializer
(useState<Theme>(() => document.documentElement.classList.contains("dark") ? "dark" : "light"))
instead of hardcoding "light" and correcting it after the fact — safe here since
ThemeProvider is already a Client Component executing after the theme-init script
has run.
src/context/ThemeContext.tsx#ThemeProviderinitializes React state unconditionally:The actual applied theme (dark or light) is decided synchronously before hydration by
the inline
themeInitScriptinlayout.tsx, which adds thedarkclass straight to<html>.ThemeProvider'suseEffectthen reads that class back to correct its ownthemestate:Between first paint and this effect running,
themeis"light"regardless of thevisitor's actual preference.
src/components/ui/ThemeToggle.tsxrenders its icondirectly from this state:
For a visitor whose page is actually rendering in dark mode (via a stored preference or
prefers-color-scheme: dark), the toggle button shows aMoonicon (meaning "click togo dark") for one render/paint even though the page is already dark — the icon is
momentarily backwards relative to the real state, a small but visible flash on every
page load in dark mode.
Suggested fix: read the initial theme synchronously via a lazy
useStateinitializer(
useState<Theme>(() => document.documentElement.classList.contains("dark") ? "dark" : "light"))instead of hardcoding
"light"and correcting it after the fact — safe here sinceThemeProvideris already a Client Component executing after the theme-init scripthas run.