Theme before hydration
Set stored preferences in a tiny inline script before React hydrates.
From vercel-react-best-practices: rendering-hydration-no-flicker and 1 more.
Reload in dark mode
Press Reload, then try a slower hydration delay.
Read in
useEffectBaduseState('light') + useEffectInline script firstGood
<script>{themeScript}</script>function ThemeWrapper({ children }: { children: ReactNode }) {
return (
<>
<div id="theme-wrapper">{children}</div>
<script
dangerouslySetInnerHTML={{
__html: `(function() {
try {
var theme = localStorage.getItem('theme') || 'light';
var el = document.getElementById('theme-wrapper');
if (el) el.className = theme;
} catch (e) {}
})();`,
}}
/>
</>
)
}
Server render, then hydrate
The server has no localStorage.
Read in renderBad
Not rendered
const theme = localStorage.getItem('theme')typeof window guardBadNot rendered
typeof window === 'undefined' ? 'light' : …Inline scriptGood
Not rendered
<div id="theme-wrapper" suppressHydrationWarning><div id="theme-wrapper" suppressHydrationWarning>{children}</div>
<script dangerouslySetInnerHTML={{ __html: themeScript }} />
Silence only expected diffs
Pick a time zone and cart count, then Hydrate.
Timestamp, no flagNoisy
Rendered at—
<span>{date.toLocaleString()}</span>Timestamp, flaggedGood
Rendered at—
<span suppressHydrationWarning>Cart count, flaggedHidden bug
Cart—
<span suppressHydrationWarning>{cart.length}function Timestamp() {
return (
<span suppressHydrationWarning>
{new Date().toLocaleString()}
</span>
)
}
Why it matters, pros and cons
Why it matters
The server can't read localStorage. An inline script fixes the first frame.
Pros
- No wrong first frame, however slow JS is
- Server output stays deterministic
- A few hundred bytes, no dependency
Cons
- Inline scripts need a CSP nonce or hash
- If a cookie can tell the server, prefer that
suppressHydrationWarningis one level deep; it hides real bugs