Capítulo 70 de 456
Client-only state (locale, timezone, theme, persisted UI) isn't available during server rendering. Use a synchronous inline <script> (via dangerouslySetInnerHTML) that runs during HTML parsing, before first paint, to correct the DOM, paired with suppressHydrationWarning so React accepts the corrected DOM instead of throwing a hydration error.
<script> inserted with dangerouslySetInnerHTML runs synchronously as the browser parses HTML, before first paint — the only technique that beats both hydration and the initial paint.suppressHydrationWarning: tells React to keep whatever is in the DOM for that element rather than the client's computed output, avoiding a hydration error/flash; without it, React client-renders from the nearest boundary and loses other inline-script corrections in that boundary.InlineScript helper: sets type="text/javascript" on server, type="text/plain" on client, to avoid React dev warnings about rendering <script> tags while still running it once on hard navigation.<Link> navigations the Client Component's own render (toLocaleDateString()) handles it directly, and the text/plain script is a no-op.useState initializer: must read from the exact same source (e.g. localStorage) as the inline script so React's initial client state matches the DOM the script already set.cookies(), but doing so in the root layout opts the whole app out of static prerendering; reading the cookie inside the inline script instead keeps the page static with no flash.export default async function Page() {
const event = await getEvent('nextjs-conf')
return (
<section>
<h1>{event.name}</h1>
<p id="event-date" suppressHydrationWarning>
{new Date(event.date).toLocaleDateString()}
</p>
<script
dangerouslySetInnerHTML={{
__html: `document.getElementById("event-date").textContent=new Date("${event.date}").toLocaleDateString()`,
}}
/>
</section>
)
}
toLocaleDateString().export function InlineScript({ html }: { html: string }) {
return (
<script
type={typeof window === 'undefined' ? 'text/javascript' : 'text/plain'}
suppressHydrationWarning
dangerouslySetInnerHTML={{ __html: html }}
/>
)
}
text/plain e é ignorado, deixando o próprio Client Component formatar direto.<html lang="en" data-theme="light" suppressHydrationWarning>
<head>
<script
dangerouslySetInnerHTML={{
__html: `(function(){try{var t=localStorage.getItem("theme");if(t)document.documentElement.setAttribute("data-theme",t)}catch(e){}})()`,
}}
/>
</head>
<body>{children}</body>
</html>
localStorage no <html> antes da pintura, no <head>.'use client'
import { useLayoutEffect } from 'react'
export function ThemeToggle() {
useLayoutEffect(() => {
const theme = localStorage.getItem('theme')
if (theme) document.documentElement.setAttribute('data-theme', theme)
}, [])
// ...
}
| Situation | Approach |
|---|---|
| Data depende de request (cookies, headers) | headers()/cookies() e formatar no servidor |
| Data atualiza ao vivo (timers, relógios) | Client Component com useEffect + suppressHydrationWarning |
| Página já totalmente dinâmica | Formatar no servidor usando Accept-Language |
| Tradução entre idiomas | internationalization (builds estáticas por locale ou renderização dinâmica) |
toLocaleDateString(): SSR usa locale do servidor, hidratação usa o do browser, React detecta mismatch e lança erro de hidratação com flash.useEffect: roda depois da hidratação e da pintura, então o usuário sempre vê o valor errado primeiro; também dispara re-render que pode reativar Suspense boundaries pais.cookies(): tira a página inteira do prerendering estático (e sob Cache Components força bloqueio de todo segmento abaixo do layout).dangerouslySetInnerHTML em <script> é bloqueado por Content-Security-Policy sem 'unsafe-inline'; requer nonce.suppressHydrationWarning é obrigatório junto do script inline: sem ele React descarta a correção e refaz o DOM do zero.useLayoutEffect evita o flash entre hidratação e pintura, mas não o flash antes da hidratação em conexões lentas — só o inline script cobre os dois.suppressHydrationWarning + inicializador lazy de estado) serve para datas, temas e qualquer estado de UI persistido (accordion aberto etc.).<html>/<head>/<body> que o script setou — reaplique via useLayoutEffect no componente dono.