fix(web): suppress hydration warning on html/body — browser extensions inject attrs

The remaining hydration mismatch was a stray `__gcrremoteframetoken`
attribute added to <html> by a browser extension after the document
loaded. Browser extensions (password managers, accessibility tools, the
Google iframe-token injector, Grammarly, etc.) routinely poke at the
top-level elements before React hydrates and React 19 then flags it as
a mismatch even though our code wasn't involved.

`suppressHydrationWarning` on <html> and <body> only suppresses
**attribute** differences on those elements; their children continue
to be hydration-checked normally, so any real bugs still show up.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
yiekheng 2026-05-10 09:00:41 +08:00
parent 65f4d2d099
commit 5c3348ef2d

View File

@ -20,11 +20,14 @@ export const viewport: Viewport = {
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
// Light theme is the only theme — no client-side theme switcher and
// therefore no pre-hydration `<script>` from a theme library. That
// avoids the "script tag while rendering" warning under React 19.
<html lang="en" className={GeistSans.className}>
<body>
// `suppressHydrationWarning` here is for *attribute* differences only.
// Browser extensions (password managers, accessibility tools, the
// Google `__gcrremoteframetoken` injector, etc.) commonly add data-/
// dunder attributes to the <html> element after the document loads,
// which React 19 otherwise flags as a hydration mismatch. Children
// are still hydration-checked normally.
<html lang="en" suppressHydrationWarning className={GeistSans.className}>
<body suppressHydrationWarning>
<AppShell>{children}</AppShell>
<Toaster richColors position="top-right" />
</body>