A «piscada» parece um bug só, mas na verdade são dois, com causas completamente diferentes. Se você diferenciar os dois primeiro, já resolveu 90%.
Step 1: Qual é o tipo de piscada?
- A. Um flash ao carregar: no momento em que a página aparece você vê uma tela branca (ou errada) e depois ela muda — por exemplo, dá um flash claro antes do modo escuro entrar, ou mostra a tela de deslogado por uma fração de segundo antes da sua tela real. → Geralmente é um hydration mismatch ou um theme flash.
- B. Piscada que não para: a tela não para de tremer ou parece que fica recarregando sem fim, a aba fica lenta, a ventoinha acelera. → Geralmente é um infinite re-render loop.
Para ter certeza, abra o DevTools do navegador com F12 → aba Console e leia o erro em vermelho:
Hydration failed/Text content did not match→ AMaximum update depth exceeded→ B
---
B. Piscada que não para: infinite re-render loop (o mais comum)
Se o console diz Maximum update depth exceeded, é isso. A causa é quase sempre a mesma: um useEffect atualiza o state, e esse novo state dispara de novo o mesmo useEffect, sem parar.
Cole isto na sua IA (Cursor / v0 / Bolt / Lovable)
> O console mostra Maximum update depth exceeded e minha tela pisca sem parar. Parece que um useEffect chama setState com um array de dependências errado, causando um loop infinito. Descubra qual useEffect é o culpado e corrija — ajuste o array de dependências ou use a forma de atualização (setCount(c => c + 1)). Corrija a causa raiz, não apenas esconda o aviso.
Se você consegue ler o código (duas correções oficiais do React)
1) Atualize com base no valor anterior — remova a dependência
// Problem: count changes every run, so the effect re-runs forever
useEffect(() => {
setCount(count + 1);
}, [count]);
// Fix: updater function + empty deps
useEffect(() => {
setCount(c => c + 1);
}, []);
2) Não passe um objeto/função como dependência — crie dentro do effect
// Problem: a new options object every render → infinite loop
const options = { serverUrl, roomId };
useEffect(() => {
createConnection(options);
}, [options]);
// Fix: build it inside the effect
useEffect(() => {
const options = { serverUrl, roomId };
createConnection(options);
}, [roomId, serverUrl]);
Checagem rápida: desconfie primeiro de qualquer useEffect(() => { ... }) sem array de dependências: sem [] ele roda a cada render e entra em loop com facilidade.
---
A. Um flash ao carregar: hydration mismatch / theme flash
Com frameworks como o Next.js, o servidor pré-renderiza o HTML e o navegador renderiza de novo na primeira pintura. Se esses dois não baterem, a tela dá um pulo uma vez. O console mostra Text content did not match / Hydration failed.
Causas comuns (segundo a documentação do Next.js)
- Usar
window,localStorageouDate()diretamente no seu código de renderização - Ramificar a UI com verificações como
typeof window !== 'undefined' - Ler o tema escuro/claro só no navegador, fazendo a primeira pintura sair com o tema errado
- Uma extensão do navegador (tradutor, extensão de modo escuro) editando o HTML → teste em uma janela anônima para confirmar
Fix 1: Adie as partes exclusivas do navegador com useEffect
Renderize a mesma coisa no servidor e na primeira pintura do cliente, e depois troque.
import { useState, useEffect } from 'react'
export default function App() {
const [isClient, setIsClient] = useState(false)
useEffect(() => { setIsClient(true) }, [])
return <h1>{isClient ? 'Browser-only content' : 'Prerendered'}</h1>
}
Fix 2: Desligue a renderização no servidor para um componente (mapas, gráficos, widgets exclusivos do navegador)
import dynamic from 'next/dynamic'
const NoSSR = dynamic(() => import('../components/no-ssr'), { ssr: false })
Fix 3: Para valores que precisam diferir (relógios, datas), silencie só aquele elemento
<time dateTime="2016-10-25" suppressHydrationWarning />
Observação: é uma saída de emergência que funciona só um nível abaixo, e o React não vai corrigir o texto que não bate — não abuse dela.
Se for um flash de modo escuro (branco → escuro)
Com o next-themes, adicione suppressHydrationWarning na tag <html> de nível mais alto, e renderize a UI que depende do tema só depois que o componente montar.
// layout.jsx (app directory)
<html lang="en" suppressHydrationWarning>
// A component that renders differently by theme
const [mounted, setMounted] = useState(false)
useEffect(() => { setMounted(true) }, [])
if (!mounted) return null
O next-themes injeta um script que roda antes de a página renderizar, então por padrão não deveria haver flash. Se ainda assim piscar, o mais provável é que esteja faltando uma das duas coisas acima (o suppressHydrationWarning, ou renderizar depois de montar).
---
Uma mensagem universal para entregar à sua IA
> Minha tela [dá um flash ao carregar / pisca sem parar]. O erro do console é [cole a mensagem exata]. Descubra o componente que está causando isso e corrija do jeito oficial. Corrija a causa raiz, não apenas esconda o aviso.
Quando ainda não há uma correção confirmada (o que dá para fazer agora)
- Desative as extensões do navegador (tradutores, complementos de modo escuro) e teste em uma janela anônima. Se parar, a extensão era a culpada.
- Copie o erro completo do console e cole na sua IA do jeito que está — a primeira linha geralmente aponta o arquivo e o número da linha exatos.
