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 matchA
  • Maximum update depth exceededB

---

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

js

// 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

js

// 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, localStorage ou Date() 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.

jsx

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)

jsx

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

jsx

<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.

jsx

// 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.