Qué significan los errores "ResizeObserver Loop Completed"
Entiende los errores de bucle de ResizeObserver, por qué el navegador aplaza notificaciones, cómo detectar bucles reales y filtrar ambos mensajes de forma segura.
“ResizeObserver loop completed with undelivered notifications” es un evento de error del navegador, no una excepción de tu código. Significa que algunas notificaciones de redimensionamiento no llegaron a entregarse en el frame actual y se aplazaron al siguiente. En la mayoría de los casos puedes ignorarlo sin problema.
Suele aparecer en el sistema de seguimiento de errores como cientos de eventos sin un stack útil, o hace fallar una ejecución de Cypress. A menudo no hay ningún ResizeObserver en tu propio código. Este artículo explica qué hace el navegador, muestra una reproducción mínima y describe cómo distinguir el ruido de un bucle real. Termina con un filtro que detecta las dos versiones del mensaje.
Puntos clave
- El navegador lanza “ResizeObserver loop completed with undelivered notifications.” como evento de error en
windowcuando las notificaciones de redimensionamiento tienen que esperar al siguiente frame. - Las versiones antiguas de los navegadores informaban de la misma condición como “ResizeObserver loop limit exceeded”, así que cualquier filtro o manejador de tests debe detectar ambos mensajes.
- Esta salvaguarda evita que el navegador se bloquee, pero no detiene el bucle: un callback que redimensiona continuamente el elemento que observa dispara el error en cada frame.
try/catchnunca captura el error, perowindow.onerror, los SDK de seguimiento de errores y el manejadoruncaught:exceptionde Cypress sí lo reciben.- Filtra el error en tu sistema de seguimiento, no silenciándolo de forma global con
window.onerror, y sigue vigilando con qué frecuencia se produce.
¿Qué significa “ResizeObserver Loop Completed With Undelivered Notifications”?
El mensaje “ResizeObserver loop completed with undelivered notifications” significa que el navegador tenía notificaciones de redimensionamiento que no pudo entregar dentro de un mismo frame y, por tanto, las aplazó. Si necesitas repasar la API en sí, consulta ResizeObserver vs window resize.
El navegador procesa las observaciones de redimensionamiento después del layout y antes del paint. Si un callback modifica el layout, el navegador vuelve a calcularlo y ejecuta los callbacks otra vez. En cada pasada solo procesa los elementos situados a mayor profundidad en el DOM que el elemento menos profundo procesado en la pasada anterior. Todo lo que no cumple esa regla espera al siguiente frame, y el navegador informa del error. La sección Observation Errors de MDN describe esta secuencia, y la especificación de Resize Observer define la regla de profundidad.
Verás uno de estos dos mensajes:
ResizeObserver loop completed with undelivered notifications.(la redacción actual, con punto final)ResizeObserver loop limit exceeded(la redacción que usaban las versiones antiguas de los navegadores)
Una reproducción mínima
Este archivo reproduce ambos comportamientos. El callback cambia el ancho del 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>
Con LOOP = false, el primer callback redimensiona la caja a 200px. Ese redimensionamiento se produce en el mismo elemento, por lo que no está a mayor profundidad que el elemento recién procesado. El navegador lo aplaza y registra el error una vez. En el siguiente frame, el callback vuelve a escribir 200px, no cambia nada y el observer deja de dispararse.
Con LOOP = true, cada frame hace la caja más ancha y el error se registra en cada frame.
¿Por qué try/catch no captura el error de ResizeObserver?
try/catch nunca captura el error de bucle de ResizeObserver porque tu callback no lanza nada: termina con normalidad. Después, el navegador despacha un evento error en window, fuera de cualquier pila de llamadas que tú controles.
try {
ro.observe(box);
} catch (err) {
console.log('never reached'); // the error is dispatched on window later
}
window.onerror y los listeners de error capturan los eventos de error a nivel de ventana, y los SDK de seguimiento de errores se enganchan a esos mismos eventos, por lo que los sistemas de seguimiento lo registran. El evento no incluye ningún objeto Error con stack trace, así que normalmente no muestran un stack útil.
¿Cuándo es el error de ResizeObserver un problema real?
El error de bucle de ResizeObserver importa cuando el layout no se estabiliza. MDN señala que la salvaguarda evita que el navegador se bloquee, pero no impide el bucle en sí. Incluso un bucle que llega a estabilizarse puede mostrar brevemente un layout intermedio mientras los cambios se reparten entre varios frames.
| Lo que ves | Lo que significa | Qué hacer |
|---|---|---|
| Unos pocos eventos tras la carga o un cambio de layout, y la página se ve estable | El bucle se estabilizó en uno o dos frames | Filtrarlo |
| Ráfagas ligadas a un componente, con un salto visible | Un componente está redimensionando la caja que observa | Corregirlo o reportarlo upstream |
| Un flujo continuo, un elemento que crece o un aumento del uso de CPU | Un bucle que nunca se estabiliza | Corregirlo de inmediato |
¿Cómo se corrige un bucle de ResizeObserver?
Para corregir un bucle de ResizeObserver, evita que el callback redimensione el elemento que observa o saca esa escritura del frame actual. Prueba estas opciones en orden.
- No redimensiones el elemento observado desde su propio callback. Escribe en un elemento cuyo tamaño no influya en la observación. Si solo buscas un comportamiento responsive visual, las container queries de CSS evitan JavaScript por completo.
- Aplaza la escritura con
requestAnimationFrame. Mover la escritura arequestAnimationFramehace que se ejecute en el siguiente frame, con lo que el error desaparece. MDN lo recomienda cuando quieres un redimensionamiento continuo. En el caso deLOOP, la caja sigue creciendo indefinidamente: lo único que desaparece es el error. - Ignora los tamaños que estableces tú mismo. Para los bucles no intencionados, MDN sugiere registrar en un
WeakMapel tamaño esperado de cada elemento:
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 el estado se almacena por elemento, se pueden observar varios elementos sin problemas. Cuando el callback detecta el ancho que él mismo estableció, omite la entrada y el bucle termina.
Las librerías de UI también lo provocan
El desencadenante habitual es un callback de ResizeObserver que cambia el tamaño del elemento que observa. Ese callback suele estar en una librería de componentes y no en tu propio código. Los componentes que miden su contenedor y luego se redimensionan a sí mismos o a un hijo son la fuente típica: por ejemplo, áreas de texto con tamaño automático, tablas virtualizadas, gráficos y popovers. Si el error aparece cuando un componente concreto se monta o se abre, actualiza la librería o reporta una reproducción upstream.
¿Cómo se filtra el error de forma segura?
Detecta ambos mensajes con un único patrón y aplícalo en tu sistema de seguimiento y en tu test runner, no en un manejador 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;
});
Cypress lanza uncaught:exception cada vez que tu aplicación genera un error que nada captura, y un manejador que devuelve false indica a Cypress que no haga fallar el test. El Catalog of Events de Cypress lo documenta. En Sentry, añade ignoreErrors: [RO_LOOP_ERROR]. Según la referencia de opciones de Sentry, las entradas de tipo string coinciden con subcadenas y las expresiones regulares se evalúan tal cual están escritas. Los SDK de JavaScript recientes de Sentry ya ignoran por defecto la redacción moderna, pero no la antigua, así que una entrada personalizada solo es necesaria para el mensaje antiguo o para SDK anteriores.
No silencies el error en window.onerror. Una supresión generalizada también oculta el caso del bucle infinito, y pierdes la señal de frecuencia. Filtrar a nivel del sistema de seguimiento, como se describe en estas buenas prácticas de registro de errores, mantiene esa decisión visible y fácil de revertir. Después, vigila el recuento. Una ráfaga ocasional tras un cambio de layout es ruido. Un flujo constante desde una misma sesión apunta a un bucle que nunca se estabiliza. Un session replay muestra si el usuario vio un salto de layout en el momento en que se produjo el error, lo que te indica si es ruido o no. El mismo triaje se aplica a otros errores ruidosos en producción, como “Failed to Fetch Dynamically Imported Module”.
Conclusión
En la mayoría de los casos, un error “ResizeObserver loop completed” significa que el navegador aplazó algunas notificaciones y el layout se estabilizó un frame después. Comprueba si el layout se estabiliza y si se produce algún salto visible. Si ambas respuestas son favorables, filtra los dos mensajes en tu sistema de seguimiento. Si no, localiza el callback que redimensiona el elemento que observa y corrígelo con un aplazamiento o con una comprobación del tamaño esperado.
Preguntas frecuentes
¿Es setTimeout o debounce una solución mejor que requestAnimationFrame?
No. Ambos sacan la escritura del redimensionamiento del frame actual, por lo que el error desaparece, pero ninguno detiene un bucle que sigue haciendo crecer el elemento. Un callback de setTimeout puede ejecutarse después de que el navegador ya haya hecho el paint, de modo que los usuarios pueden ver el layout antiguo durante un frame antes de que se aplique el cambio. requestAnimationFrame se ejecuta antes del siguiente paint, lo que mantiene la actualización sincronizada con el renderizado.
¿Por qué aparece el error justo después de que se monta un componente?
ResizeObserver envía una primera notificación cuando se llama a observe() sobre un elemento que está renderizado y tiene un tamaño distinto de cero. Si el callback escribe un nuevo tamaño en ese elemento durante esta primera notificación, el navegador aplaza la notificación siguiente e informa del error de inmediato. Los componentes que se miden y se redimensionan a sí mismos al montarse lo provocan de esta forma, aunque no se redimensione nada en la página.
¿Pueden las container queries de CSS sustituir a un callback de ResizeObserver?
Sí, cuando el callback solo cambia estilos en función del ancho de un contenedor. Las container size queries aplican size containment, de modo que el contenedor no toma su tamaño de su contenido y los estilos no pueden influir en la medición. Las size queries funcionan en Chrome 105, Safari 16, Firefox 110 y versiones posteriores. Reserva ResizeObserver para las tareas que CSS no puede hacer, como recalcular las dimensiones de un gráfico o el número de filas virtualizadas.
¿Cómo encuentro el código que provoca el error cuando el stack trace está vacío?
En desarrollo, sustituye temporalmente window.ResizeObserver por una subclase que registre cada elemento observado y envuelva el callback. Después, reproduce el error y comprueba qué elemento se redimensionó justo antes del evento de error en window. Si el callback está dentro de una librería de terceros, eliminar componentes uno a uno hasta que el error desaparezca permite acotar el origen.