Ce que signifient les erreurs « ResizeObserver Loop Completed »
Comprenez les erreurs de boucle ResizeObserver, pourquoi le navigateur reporte des notifications, comment repérer les boucles réelles et filtrer les deux messages en toute sécurité.
« ResizeObserver loop completed with undelivered notifications » est un événement d’erreur émis par le navigateur, et non une exception levée par votre code. Il signifie que certaines notifications de redimensionnement n’ont pas pu être traitées dans la frame courante et ont été reportées à la suivante. Dans la plupart des cas, vous pouvez l’ignorer sans risque.
Cette erreur apparaît généralement dans un outil de suivi des erreurs sous la forme de centaines d’événements sans stack trace exploitable, ou bien elle fait échouer une exécution Cypress, alors même que votre propre code ne contient souvent aucun ResizeObserver. Cet article explique ce que fait le navigateur, propose une reproduction minimale et montre comment distinguer le bruit d’une véritable boucle. Il se termine par un filtre qui couvre les deux versions du message.
Points clés
- Le navigateur déclenche « ResizeObserver loop completed with undelivered notifications. » sous la forme d’un événement d’erreur sur
windowlorsque des notifications de redimensionnement doivent attendre la frame suivante. - Les anciennes versions des navigateurs signalaient la même situation avec le message « ResizeObserver loop limit exceeded » : tout filtre ou gestionnaire de test doit donc reconnaître les deux messages.
- Ce garde-fou empêche le navigateur de se bloquer, mais il n’arrête pas la boucle : un callback qui redimensionne en permanence l’élément qu’il observe déclenche l’erreur à chaque frame.
try/catchne voit jamais cette erreur, contrairement àwindow.onerror, aux SDK de suivi des erreurs et au gestionnaireuncaught:exceptionde Cypress.- Filtrez l’erreur dans votre outil de suivi plutôt qu’en la neutralisant globalement via
window.onerror, et continuez à surveiller sa fréquence.
Que signifie « ResizeObserver Loop Completed With Undelivered Notifications » ?
Le message « ResizeObserver loop completed with undelivered notifications » indique que le navigateur disposait de notifications de redimensionnement qu’il n’a pas pu délivrer au cours d’une même frame et qu’il les a donc reportées. Pour un rappel sur l’API elle-même, consultez ResizeObserver vs window resize.
Le navigateur traite les observations de redimensionnement après le calcul du layout et avant le rendu (paint). Si un callback modifie le layout, le navigateur recalcule celui-ci et exécute de nouveau les callbacks. À chaque passe, il ne traite que les éléments situés plus profondément dans le DOM que l’élément le moins profond traité lors de la passe précédente. Tout ce qui ne respecte pas cette règle attend la frame suivante, et le navigateur signale l’erreur. La section Observation Errors de MDN décrit cette séquence, et la spécification Resize Observer définit la règle de profondeur.
Vous rencontrerez l’un de ces deux messages :
ResizeObserver loop completed with undelivered notifications.(la formulation actuelle, avec un point final)ResizeObserver loop limit exceeded(la formulation utilisée par les anciennes versions des navigateurs)
Une reproduction minimale
Ce fichier reproduit les deux comportements. Le callback modifie la largeur de l’élément qu’il observe.
<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>
Avec LOOP = false, le premier callback redimensionne la boîte à 200px. Ce redimensionnement concerne le même élément, qui n’est donc pas plus profond que celui qui vient d’être traité. Le navigateur le reporte et journalise l’erreur une seule fois. À la frame suivante, le callback écrit de nouveau 200px, rien ne change et l’observer cesse de se déclencher.
Avec LOOP = true, chaque frame élargit la boîte et l’erreur est journalisée à chaque frame.
Pourquoi try/catch n’intercepte-t-il pas l’erreur ResizeObserver ?
try/catch n’intercepte jamais l’erreur de boucle ResizeObserver, car votre callback ne lève aucune exception : il se termine normalement. Le navigateur émet ensuite un événement error sur window, en dehors de toute pile d’appels que vous contrôlez.
try {
ro.observe(box);
} catch (err) {
console.log('never reached'); // the error is dispatched on window later
}
window.onerror et les écouteurs error interceptent les événements d’erreur au niveau de window. Les SDK de suivi des erreurs se branchent sur ces mêmes événements, si bien que les outils de suivi les enregistrent. L’événement ne transporte aucun objet Error doté d’une stack trace, c’est pourquoi les outils de suivi n’affichent généralement aucune pile exploitable.
Quand l’erreur ResizeObserver pose-t-elle un vrai problème ?
L’erreur de boucle ResizeObserver devient préoccupante lorsque le layout ne se stabilise pas. MDN souligne que le garde-fou empêche le navigateur de se bloquer, mais pas la boucle elle-même. Même une boucle qui finit par se stabiliser peut afficher brièvement un layout intermédiaire pendant que les modifications sont réparties sur plusieurs frames.
| Ce que vous observez | Ce que cela signifie | Que faire |
|---|---|---|
| Quelques événements après le chargement ou un changement de layout, et la page semble stable | La boucle s’est stabilisée en une ou deux frames | La filtrer |
| Des rafales liées à un composant précis, avec un saut visible | Un composant redimensionne la boîte qu’il observe | Corriger le problème ou le signaler en amont |
| Un flux continu, un élément qui grandit ou une hausse de la consommation CPU | Une boucle qui ne se stabilise jamais | Corriger immédiatement |
Comment corriger une boucle ResizeObserver ?
Pour corriger une boucle ResizeObserver, empêchez le callback de redimensionner l’élément qu’il observe, ou déplacez cette écriture hors de la frame courante. Examinez ces options dans l’ordre.
- Ne redimensionnez pas l’élément observé depuis son propre callback. Écrivez plutôt dans un élément dont la taille n’influe pas sur l’observation. Pour une adaptation purement visuelle, les container queries CSS permettent de se passer entièrement de JavaScript.
- Différez avec
requestAnimationFrame. Déplacer l’écriture dansrequestAnimationFramel’exécute à la frame suivante, ce qui fait disparaître l’erreur. MDN recommande cette approche lorsque vous souhaitez un redimensionnement continu. Dans le casLOOP, la boîte continue de grandir indéfiniment : seule l’erreur disparaît. - Ignorez les tailles que vous avez vous-même définies. Pour les boucles non intentionnelles, MDN suggère d’enregistrer la taille attendue pour chaque élément dans une
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);
});
}
});
Comme l’état est stocké par élément, observer plusieurs éléments ne pose aucun problème. Lorsque le callback constate la largeur qu’il a lui-même définie, il ignore l’entrée et la boucle s’arrête.
Les bibliothèques d’interface la déclenchent aussi
Le déclencheur habituel est un callback ResizeObserver qui modifie la taille de l’élément qu’il observe. Ce callback se trouve souvent dans une bibliothèque de composants plutôt que dans votre propre code. Les composants qui mesurent leur conteneur puis se redimensionnent eux-mêmes ou redimensionnent un enfant en sont la source typique : zones de texte à hauteur automatique, tableaux virtualisés, graphiques ou popovers, par exemple. Si l’erreur apparaît lorsqu’un composant particulier est monté ou ouvert, mettez à jour la bibliothèque ou signalez le problème en amont avec une reproduction.
Comment filtrer l’erreur en toute sécurité ?
Reconnaissez les deux messages avec un seul motif, et appliquez-le dans votre outil de suivi et votre test runner, et non dans un gestionnaire 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 déclenche uncaught:exception chaque fois que votre application lève une erreur que rien n’intercepte, et un gestionnaire qui renvoie false indique à Cypress de ne pas faire échouer le test. Le Catalog of Events de Cypress documente ce comportement. Dans Sentry, ajoutez ignoreErrors: [RO_LOOP_ERROR]. D’après la référence des options de Sentry, les entrées de type chaîne correspondent à des sous-chaînes, tandis que les expressions régulières sont évaluées telles quelles. Les SDK JavaScript récents de Sentry ignorent déjà par défaut la formulation moderne, mais pas l’ancienne : une entrée personnalisée n’est donc nécessaire que pour l’ancienne chaîne ou pour les SDK plus anciens.
Ne neutralisez pas l’erreur dans window.onerror. Une suppression globale masque également le cas de la boucle infinie, et vous perdez l’indicateur de fréquence. Filtrer au niveau de l’outil de suivi, comme le préconisent ces bonnes pratiques de journalisation des erreurs, permet de garder cette décision visible et facilement réversible. Surveillez ensuite le nombre d’occurrences. Une rafale occasionnelle après un changement de layout relève du bruit. Un flux continu provenant d’une même session révèle une boucle qui ne se stabilise jamais. Un session replay montre si l’utilisateur a vu un saut de layout au moment où l’erreur s’est déclenchée, ce qui vous indique s’il s’agit ou non de bruit. Le même tri s’applique à d’autres erreurs bruyantes en production, comme « Failed to Fetch Dynamically Imported Module ».
Conclusion
Dans la plupart des cas, une erreur « ResizeObserver loop completed » signifie que le navigateur a reporté certaines notifications et que le layout s’est stabilisé une frame plus tard. Vérifiez si le layout se stabilise et si un élément saute visiblement. Si les deux réponses sont rassurantes, filtrez les deux messages dans votre outil de suivi. Sinon, identifiez le callback qui redimensionne l’élément qu’il observe et corrigez-le à l’aide d’un report d’exécution ou d’une vérification de la taille attendue.
FAQ
setTimeout ou un debounce sont-ils de meilleures solutions que requestAnimationFrame ?
Non. Les deux déplacent l'écriture du redimensionnement hors de la frame courante, ce qui fait disparaître l'erreur, mais aucun des deux n'arrête une boucle qui continue d'agrandir l'élément. Un callback setTimeout peut s'exécuter après que le navigateur a déjà effectué le rendu, si bien que les utilisateurs risquent de voir l'ancien layout pendant une frame avant que la modification ne s'applique. requestAnimationFrame s'exécute avant le prochain rendu, ce qui garde la mise à jour synchronisée avec l'affichage.
Pourquoi l'erreur apparaît-elle juste après le montage d'un composant ?
ResizeObserver envoie une première notification lorsque observe() est appelé sur un élément rendu dont la taille est non nulle. Si le callback écrit une nouvelle taille sur cet élément lors de cette première notification, le navigateur reporte la notification suivante et signale immédiatement l'erreur. Les composants qui se mesurent et se redimensionnent au montage la déclenchent de cette manière, même si aucun élément de la page n'est redimensionné.
Les container queries CSS peuvent-elles remplacer un callback ResizeObserver ?
Oui, lorsque le callback se contente de modifier des styles en fonction de la largeur d'un conteneur. Les container size queries appliquent un confinement de taille : le conteneur ne tire pas sa taille de son contenu, et les styles ne peuvent donc pas influer sur la mesure. Les size queries sont prises en charge à partir de Chrome 105, Safari 16 et Firefox 110. Réservez ResizeObserver aux traitements que CSS ne peut pas assurer, comme le recalcul des dimensions d'un graphique ou du nombre de lignes d'une liste virtualisée.
Comment identifier le code qui déclenche l'erreur lorsque la stack trace est vide ?
En développement, remplacez temporairement window.ResizeObserver par une sous-classe qui journalise chaque élément observé et encapsule le callback. Reproduisez ensuite l'erreur et vérifiez quel élément a été redimensionné juste avant l'événement d'erreur sur window. Si le callback se trouve dans une bibliothèque tierce, retirer les composants un par un jusqu'à ce que l'erreur disparaisse permet de cerner la source.