fix(theme): suppress hydration warning on root html data-theme (#207)

The `theme-boot` beforeInteractive script in layout.tsx rewrites
`data-theme` on <html> from localStorage before React hydrates, so for
any non-default theme the client DOM intentionally differs from the
server-rendered DEFAULT_THEME ("violet"). React then logged a hydration
mismatch on <html> on every load where the saved theme differed.

Add suppressHydrationWarning to the root <html>. It's scoped to that
element's own attributes (child mismatches still surface) and preserves
the documented localStorage-only/device-scoped theme design — unlike a
cookie-based SSR approach, which would change that behavior.

Verified in-browser (Next 16.2.6 / Turbopack): with a non-default theme
in localStorage, the reproduced "+ data-theme=violet / - data-theme=cobalt"
hydration error is gone; removing the prop brings it back (negative
control). typecheck, lint (0 errors), 370 tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Arnas Donauskas 2026-06-05 13:46:23 +03:00
parent 0a9e2db61a
commit 7c373c8e0a
No known key found for this signature in database

View File

@ -70,6 +70,13 @@ export default function RootLayout({
lang="en" lang="en"
data-theme={DEFAULT_THEME} data-theme={DEFAULT_THEME}
className={`${inter.variable} h-full antialiased`} className={`${inter.variable} h-full antialiased`}
// The `theme-boot` script below rewrites `data-theme` on <html>
// from localStorage before React hydrates, so for any non-default
// theme the client DOM intentionally differs from the server-
// rendered `DEFAULT_THEME`. suppressHydrationWarning silences the
// expected mismatch — it only applies to this element's own
// attributes, so genuine mismatches in children still surface.
suppressHydrationWarning
> >
<head> <head>
<Script <Script