Skip to main content
Console Errors

How to Fix 'ResizeObserver loop completed with undelivered notifications'

What the ResizeObserver loop error means, why it appears in your console, and whether you actually need to fix it.

Updated

How to Fix “ResizeObserver loop completed with undelivered notifications”

This error shows up in the console unprompted and provides zero useful context:

ResizeObserver loop completed with undelivered notifications.

Or the older variant:

ResizeObserver loop limit exceeded

It means a ResizeObserver callback changed the size of an observed element, which triggered another resize observation, which would trigger another callback, and so on. The browser detected this potential infinite loop and stopped delivering notifications for that frame. The observation still works on the next frame.

In most cases, this error is harmless. Your layout still works. Nothing is broken. But it fills up the console, fails error monitoring checks, and can mask real issues. It’s worth understanding when to ignore it and when to fix it.

What ResizeObserver does

ResizeObserver watches elements for size changes and calls your callback when one happens. It’s used by virtualized lists, responsive components, chart libraries, auto-sizing textareas, and pretty much any UI that adapts to container dimensions.

const observer = new ResizeObserver((entries) => {
  for (const entry of entries) {
    console.log('New size:', entry.contentRect.width, entry.contentRect.height)
  }
})

observer.observe(document.querySelector('.container'))

The browser processes all ResizeObserver callbacks after layout but before paint. If your callback changes the DOM in a way that triggers another layout, the browser needs to recalculate sizes and potentially fire more callbacks. It allows one level of this recursion, then stops and throws the error.

When it’s harmless

Most of the time, this error fires because a library (not your code) is using ResizeObserver internally. Common sources:

  • AG Grid, DataTables, and other table libraries resize columns based on content
  • Chart.js, D3, Recharts resize canvases to fit containers
  • React virtualized lists (react-window, react-virtuoso) recalculate row heights
  • CSS-in-JS libraries that measure elements for style calculation
  • Browser extensions injecting resize observers on every page

If your UI looks correct and behaves correctly, the error is cosmetic. The browser deferred some notifications to the next frame, and the next frame handled them fine.

When it’s a real problem

The error becomes a real problem when your layout visibly flickers, jitters, or never settles. This happens when:

  1. Your callback changes an element’s size, which triggers a new observation, which changes the size again, in a genuine infinite loop
  2. The deferred notifications cause a visible one-frame delay in layout updates
  3. Your error monitoring tool (Sentry, Datadog) counts it as an unhandled error and creates noise

Example of a real problem

const observer = new ResizeObserver((entries) => {
  for (const entry of entries) {
    // This changes the element's height, which triggers another resize
    entry.target.style.height = entry.contentRect.width * 0.5 + 'px'
  }
})

observer.observe(document.querySelector('.aspect-ratio-box'))

If the width depends on the height (through flexbox or grid), this creates a feedback loop. Each resize triggers a new size, which triggers another resize.

How to fix it

1. Use CSS instead of JavaScript for layout calculations

If you’re using ResizeObserver to maintain aspect ratios, equal heights, or responsive sizing, CSS likely handles it natively now.

/* Instead of ResizeObserver for aspect ratio */
.aspect-ratio-box {
  aspect-ratio: 2 / 1;
}

/* Instead of ResizeObserver for container queries */
@container (min-width: 400px) {
  .card {
    flex-direction: row;
  }
}

Container queries and aspect-ratio eliminate the need for JavaScript-based resize handling in many cases.

2. Debounce the callback

If you must use ResizeObserver and the callback modifies layout, debounce it so it only runs once per animation frame:

function createDebouncedResizeObserver(callback) {
  let rafId = null

  const observer = new ResizeObserver((entries) => {
    if (rafId) cancelAnimationFrame(rafId)
    rafId = requestAnimationFrame(() => {
      callback(entries)
      rafId = null
    })
  })

  return observer
}

const observer = createDebouncedResizeObserver((entries) => {
  for (const entry of entries) {
    updateLayout(entry.target, entry.contentRect)
  }
})

This prevents the callback from firing multiple times in the same frame, breaking the loop.

3. Avoid modifying observed elements inside the callback

The simplest fix: don’t change the size of the element you’re observing inside the callback. Observe one element, modify a different one.

// Bad: modifying the observed element
const observer = new ResizeObserver((entries) => {
  entries[0].target.style.height = '100px' // triggers another observation
})

// Good: observe parent, modify child
const observer = new ResizeObserver((entries) => {
  const width = entries[0].contentRect.width
  document.querySelector('.child').style.height = width * 0.5 + 'px'
})

observer.observe(document.querySelector('.parent'))

4. Suppress the error in error monitoring

If the error is genuinely harmless in your app and you’ve verified the layout works correctly, filter it out of your error monitoring:

// Sentry
import * as Sentry from '@sentry/browser'

Sentry.init({
  dsn: 'your-dsn',
  ignoreErrors: [
    'ResizeObserver loop completed with undelivered notifications',
    'ResizeObserver loop limit exceeded',
  ],
})

For global suppression in development only:

if (import.meta.env.DEV) {
  const originalError = window.onerror
  window.onerror = (message, ...args) => {
    if (typeof message === 'string' && message.includes('ResizeObserver loop')) {
      return true
    }
    return originalError?.(message, ...args)
  }
}

Don’t suppress this globally in production unless you’re confident it’s never a real issue.

The bottom line

This error is annoying because it looks alarming but usually means nothing. Check your layout visually. If everything looks right, the browser handled it. If you see flickering or layout jitter, you have a real feedback loop to fix.

The fix is almost always to replace JavaScript-based layout with CSS, or to stop modifying the observed element inside the callback.

Hushbug categorizes console errors by severity so you can tell real problems from harmless noise automatically while you browse. Coming soon to the Chrome Web Store.