O que significam os erros "ResizeObserver Loop Completed"
Entenda erros de loop do ResizeObserver, por que o navegador adia notificações, como identificar loops reais e filtrar com segurança as duas mensagens.
“ResizeObserver loop completed with undelivered notifications” é um evento de erro do navegador, não uma exceção lançada pelo seu código. Ele indica que algumas notificações de redimensionamento não foram entregues no frame atual e foram adiadas para o próximo. Na maioria dos casos, você pode ignorá-lo com segurança.
Esse erro costuma aparecer no seu error tracker como centenas de eventos sem nenhuma stack útil, ou faz uma execução do Cypress falhar, e muitas vezes não há nenhum ResizeObserver no seu próprio código. Este artigo explica o que o navegador está fazendo, mostra uma reprodução mínima e descreve como diferenciar ruído de um loop real. No final, há um filtro que captura as duas versões da mensagem.
Principais conclusões
- O navegador dispara “ResizeObserver loop completed with undelivered notifications.” como um evento de erro em
windowquando as notificações de redimensionamento precisam esperar até o próximo frame. - Versões mais antigas dos navegadores reportavam a mesma condição como “ResizeObserver loop limit exceeded”, então qualquer filtro ou handler de testes precisa capturar as duas mensagens.
- O mecanismo de proteção impede que o navegador trave, mas não interrompe o loop: um callback que redimensiona continuamente o elemento que observa dispara o erro a cada frame.
- O
try/catchnunca captura o erro, maswindow.onerror, SDKs de error tracking e o handleruncaught:exceptiondo Cypress capturam. - Filtre o erro no seu tracker, não com um
window.onerrorglobal que o descarte, e continue acompanhando a frequência com que ele ocorre.
O que significa “ResizeObserver Loop Completed With Undelivered Notifications”?
A mensagem “ResizeObserver loop completed with undelivered notifications” significa que o navegador tinha notificações de redimensionamento que não conseguiu entregar dentro de um único frame e, por isso, as adiou. Se precisar relembrar como a API funciona, veja ResizeObserver vs window resize.
O navegador processa as observações de redimensionamento depois do layout e antes do paint. Se um callback altera o layout, o navegador recalcula e executa os callbacks novamente. A cada passada, ele só processa elementos mais profundos no DOM do que o elemento mais raso processado na passada anterior. Tudo o que não atende a essa regra espera pelo próximo frame, e o navegador reporta o erro. A seção Observation Errors da MDN descreve essa sequência, e a especificação do Resize Observer define a regra de profundidade.
Você verá uma de duas mensagens:
ResizeObserver loop completed with undelivered notifications.(a redação atual, com ponto final)ResizeObserver loop limit exceeded(a redação usada por versões mais antigas dos navegadores)
Uma reprodução mínima
Este arquivo reproduz os dois comportamentos. O callback altera a largura do elemento que está observando.
<div id="box" style="width:100px;height:40px;background:tomato"></div>
<script>
const box = document.getElementById('box');
const LOOP = false; // set to true for the endless case
window.addEventListener('error', (e) => console.log('window error:', e.message));
const ro = new ResizeObserver(([entry]) => {
const width = entry.contentBoxSize[0].inlineSize;
entry.target.style.width = LOOP ? `${width + 10}px` : '200px';
});
ro.observe(box);
</script>
Com LOOP = false, o primeiro callback redimensiona a caixa para 200px. Esse redimensionamento ocorre no mesmo elemento, portanto não é mais profundo do que o elemento que acabou de ser processado. O navegador o adia e registra o erro uma única vez. No frame seguinte, o callback escreve 200px novamente, nada muda e o observer para de disparar.
Com LOOP = true, cada frame deixa a caixa mais larga, e o erro é registrado a cada frame.
Por que o try/catch não captura o erro do ResizeObserver?
O try/catch nunca captura o erro de loop do ResizeObserver porque o seu callback não lança nada. Ele termina normalmente. Depois disso, o navegador dispara um evento error em window, fora de qualquer call stack que você controle.
try {
ro.observe(box);
} catch (err) {
console.log('never reached'); // the error is dispatched on window later
}
O window.onerror e os listeners de error capturam eventos de erro no nível de window, e os SDKs de error tracking se conectam a esses mesmos eventos, por isso os trackers o registram. O evento não carrega nenhum objeto Error com stack trace, então os trackers geralmente não exibem nenhuma stack útil.
Quando o erro do ResizeObserver é um problema real?
O erro de loop do ResizeObserver importa quando o layout não se estabiliza. A MDN destaca que o mecanismo de proteção evita que o navegador trave, mas não impede o loop em si. Mesmo um loop que se estabiliza pode exibir brevemente um layout intermediário enquanto as alterações são distribuídas entre frames.
| O que você observa | O que significa | O que fazer |
|---|---|---|
| Alguns eventos após o carregamento ou uma mudança de layout, e a página parece estável | O loop se estabilizou em um ou dois frames | Filtre o erro |
| Rajadas ligadas a um componente específico, com um salto visível | Um componente está redimensionando a caixa que observa | Corrija ou reporte ao projeto upstream |
| Um fluxo contínuo, um elemento que cresce ou aumento no uso de CPU | Um loop que nunca se estabiliza | Corrija imediatamente |
Como corrigir um loop do ResizeObserver?
Corrija um loop do ResizeObserver impedindo que o callback redimensione o elemento que observa ou movendo essa escrita para fora do frame atual. Siga estas opções na ordem.
- Não redimensione o elemento observado a partir do próprio callback. Escreva em um elemento cujo tamanho não realimente a observação. Para responsividade puramente visual, as container queries do CSS dispensam o JavaScript por completo.
- Adie com
requestAnimationFrame. Mover a escrita para dentro de umrequestAnimationFramefaz com que ela seja executada no próximo frame, e o erro deixa de aparecer. A MDN recomenda essa abordagem quando você quer um redimensionamento contínuo. No casoLOOP, a caixa continua crescendo indefinidamente. Só o erro desaparece. - Ignore os tamanhos que você mesmo definiu. Para loops não intencionais, a MDN sugere registrar o tamanho esperado de cada elemento em um
WeakMap:
const expected = new WeakMap();
const ro = new ResizeObserver((entries) => {
for (const entry of entries) {
const width = entry.contentBoxSize[0].inlineSize;
if (expected.get(entry.target) === width) continue; // our own change
requestAnimationFrame(() => {
const next = width + 10;
entry.target.style.width = `${next}px`;
expected.set(entry.target, next);
});
}
});
Como o estado é armazenado por elemento, observar vários elementos é seguro. Quando o callback encontra a largura que ele mesmo definiu, ele ignora a entrada e o loop termina.
Bibliotecas de UI também disparam o erro
O gatilho mais comum é um callback de ResizeObserver que altera o tamanho do elemento que observa. Muitas vezes, esse callback está em uma biblioteca de componentes, e não no seu próprio código. Componentes que medem o próprio contêiner e depois redimensionam a si mesmos ou a um filho são a origem típica, como text areas com redimensionamento automático, tabelas virtualizadas, gráficos e popovers. Se o erro aparece quando um componente específico é montado ou aberto, atualize a biblioteca ou reporte uma reprodução ao projeto upstream.
Como filtrar o erro com segurança?
Capture as duas mensagens com um único padrão e aplique-o no seu tracker e no seu test runner, não em um handler global.
const RO_LOOP_ERROR =
/ResizeObserver loop (completed with undelivered notifications|limit exceeded)/;
// cypress/support/e2e.js
Cypress.on('uncaught:exception', (err) => {
if (RO_LOOP_ERROR.test(err.message)) return false;
});
O Cypress dispara uncaught:exception sempre que sua aplicação lança um erro que nada captura, e um handler que retorna false instrui o Cypress a não falhar o teste. O Catalog of Events do Cypress documenta esse comportamento. No Sentry, adicione ignoreErrors: [RO_LOOP_ERROR]. De acordo com a referência de opções do Sentry, entradas do tipo string correspondem a substrings, e entradas regex são testadas exatamente como foram escritas. Os SDKs JavaScript recentes do Sentry já ignoram a redação moderna por padrão, mas não a antiga, então uma entrada personalizada só é necessária para a string antiga ou para SDKs mais antigos.
Não descarte o erro no window.onerror. Uma supressão generalizada também esconde o caso de loop infinito, e você perde o sinal de frequência. Filtrar no nível do tracker, como descrito nestas boas práticas de logging de erros, mantém essa decisão visível e fácil de reverter. Depois, acompanhe a contagem. Uma rajada ocasional após uma mudança de layout é ruído. Um fluxo constante vindo de uma única sessão aponta para um loop que nunca se estabiliza. Um session replay mostra se o usuário viu um salto de layout no momento em que o erro foi disparado, o que indica se é ou não ruído. A mesma triagem se aplica a outros erros ruidosos em produção, como “Failed to Fetch Dynamically Imported Module”.
Conclusão
Na maioria dos casos, um erro “ResizeObserver loop completed” significa que o navegador adiou algumas notificações e o layout se estabilizou um frame depois. Verifique se o layout se estabiliza e se algo salta visivelmente na tela. Se as duas respostas forem positivas, filtre as duas mensagens no seu tracker. Caso contrário, encontre o callback que redimensiona o elemento que observa e corrija-o com um adiamento ou com uma verificação de tamanho esperado.
Perguntas frequentes
setTimeout ou debounce são uma correção melhor do que requestAnimationFrame?
Não. Ambos movem a escrita de redimensionamento para fora do frame atual, então o erro deixa de aparecer, mas nenhum deles interrompe um loop que continua aumentando o elemento. Um callback de setTimeout pode ser executado depois que o navegador já fez o paint, então os usuários podem ver o layout antigo por um frame antes de a alteração ser aplicada. O requestAnimationFrame é executado antes do próximo paint, o que mantém a atualização sincronizada com a renderização.
Por que o erro aparece logo depois que um componente é montado?
O ResizeObserver envia uma primeira notificação quando observe() é chamado em um elemento renderizado e com tamanho diferente de zero. Se o callback escreve um novo tamanho nesse elemento durante essa primeira notificação, o navegador adia a notificação seguinte e reporta o erro imediatamente. Componentes que se medem e se redimensionam na montagem disparam o erro dessa forma, mesmo quando nada na página é redimensionado.
As container queries do CSS podem substituir um callback do ResizeObserver?
Sim, quando o callback apenas altera estilos com base na largura de um contêiner. As container size queries aplicam size containment, de modo que o contêiner não deriva seu tamanho do conteúdo e a estilização não consegue realimentar a medição. As size queries funcionam a partir do Chrome 105, Safari 16 e Firefox 110. Mantenha o ResizeObserver para tarefas que o CSS não consegue realizar, como recalcular as dimensões de gráficos ou a quantidade de linhas virtualizadas.
Como descobrir qual código dispara o erro quando o stack trace está vazio?
Em desenvolvimento, substitua temporariamente window.ResizeObserver por uma subclasse que registre cada elemento observado e envolva o callback. Em seguida, reproduza o erro e verifique qual elemento foi redimensionado logo antes do evento de erro em window. Se o callback estiver dentro de uma biblioteca de terceiros, remover os componentes um a um até o erro parar ajuda a identificar a origem.