Lighthouse audit forced-reflow-insight · Performance

"Forced reflow while executing JavaScript": how to fix it

Quick answer: A forced reflow happens when JavaScript reads layout values like offsetWidth or getBoundingClientRect() right after changing the DOM, so the browser must recalculate layout synchronously. Lighthouse 13 lists these as the Forced reflow insight. Fix it by reading all values before writing, deferring writes to requestAnimationFrame, and using ResizeObserver instead of polling geometry.
View raw .md for LLMs / your notes

This audit flags JavaScript that reads layout geometry, such as offsetWidth or getBoundingClientRect(), right after it changed the DOM or styles. The browser must stop and recalculate layout on the spot to answer. Lighthouse 13 shows it as the Forced reflow insight. Chrome DevTools prints the same problem in the console as "[Violation] Forced reflow while executing JavaScript took 45ms". In our study of 269 Shopify storefronts, 208 of them (77.3%) failed it. This page covers what the report rows mean, how to find the line that causes it, and how to fix it in your own code and in third-party scripts.

TL;DR

What does "Forced reflow while executing JavaScript" mean?

The browser normally batches layout work. You change styles, your script finishes, and the browser lays out the page once before the next paint. A forced reflow breaks that batch. If your script asks for a geometry value after a change, the browser cannot use the old layout, so it runs layout immediately.

// Forces a reflow on every iteration: write, then read.
for (const card of cards) {
  card.style.height = 'auto';            // write: invalidates layout
  const h = card.offsetHeight;           // read: forces layout now
  card.style.height = `${h + 20}px`;     // write again
}

Lighthouse finds these synchronous layouts in the page load trace and lists them:

Part of the reportWhat it tells you
Top function callThe one function that spent the most time in forced reflows. Start here.
SourceEach script location (URL and line) that triggered a reflow
Total reflow timeMilliseconds of layout work that location forced during load
[unattributed]Reflow time Lighthouse could not tie to a script, often from inline code or browser extensions in the test run

The insight does not change your Performance score by itself. It feeds the score through the metrics: forced layout is main-thread time, so it adds to long tasks and main-thread work.

Which JavaScript properties force a reflow?

Any read that needs current geometry or computed style forces layout if the DOM changed since the last layout. The common ones:

GroupExamples
Element boxoffsetWidth, offsetHeight, offsetTop, offsetLeft, clientWidth, clientHeight
ScrollscrollTop, scrollHeight, scrollWidth, scrollIntoView(), scrollTo()
Geometry methodsgetBoundingClientRect(), getClientRects()
Computed stylewindow.getComputedStyle() and reading values from it
Text and focusinnerText, focus()
WindowinnerWidth, innerHeight, scrollX, scrollY

A read on its own is cheap. The cost comes from a read that follows a write in the same task.

How do I find which script causes the forced reflow?

  1. In the Lighthouse report, open the Performance section and expand Forced reflow. Note the top function call and each source URL with its line number.
  2. In the DevTools console, set the log level to include Verbose. Chrome then prints [Violation] Forced reflow while executing JavaScript took Nms for each slow one. Click the source link next to the message.
  3. In the DevTools Performance panel, record a page load. Purple Layout blocks with a red corner are forced reflows. Select one and the summary shows the warning "Forced reflow is a likely performance bottleneck" and the stack trace that caused it. The Insights sidebar lists the same Forced reflow insight as Lighthouse.
  4. Sort by ownership. A URL on your own domain or theme assets folder is yours. A URL on a vendor CDN (chat widgets, review apps, analytics) belongs to the vendor.

How do I fix forced reflow in my own code?

1. Batch reads before writes

Read every value you need first, then apply every change. The browser then lays out once.

// One layout for the whole loop: read all, then write all.
const heights = cards.map((card) => card.offsetHeight);   // reads
cards.forEach((card, i) => {
  card.style.height = `${heights[i] + 20}px`;             // writes
});

2. Defer writes to the next frame

If a read must come after a write, move the write into requestAnimationFrame. The browser runs it just before the next paint, after its own layout.

const width = el.offsetWidth;              // read now
requestAnimationFrame(() => {
  other.style.width = `${width}px`;        // write before next paint
});

3. Use observers instead of polling geometry

Scroll and resize handlers that call getBoundingClientRect() force layout on every event. Observers report size and visibility without that cost.

// Instead of reading offsetWidth on every resize event:
const ro = new ResizeObserver((entries) => {
  for (const entry of entries) {
    const { inlineSize } = entry.contentBoxSize[0];
    entry.target.classList.toggle('compact', inlineSize < 480);
  }
});
ro.observe(document.querySelector('.product-grid'));

Use IntersectionObserver for lazy loading, sticky headers, and "is this element on screen" checks.

4. Cache values that do not change

If you read window.innerWidth or an element height many times in one task, read it once and keep it in a variable. Refresh it in a ResizeObserver callback, not on every use.

5. Animate with transform and opacity

Changing top, left, width, or height in an animation loop invalidates layout every frame, and any read in that loop forces it. Animate transform and opacity instead. They do not trigger layout.

How do I fix forced reflow in Shopify, WordPress, or React?

Can I fix forced reflow in a third-party script?

You cannot edit vendor code. Your options:

  1. Load it later. Start chat widgets and review apps after window.load or on first user interaction. The reflow still happens, but not during load, so it no longer delays the page.
  2. Update the SDK. Vendors fix layout thrashing in new versions.
  3. Report it. Send the vendor the source URL, line, and reflow time from the report.
  4. Remove the tag if it does not earn its place. This is the only fix you fully control.

How do I verify the fix?

  1. Re-run Lighthouse. The source rows for your code should be gone, or their total reflow time should be lower.
  2. Record a load in the DevTools Performance panel. The red-cornered Layout blocks under your function should be gone.
  3. Check the console with Verbose on. The [Violation] Forced reflow messages from your files should stop.
  4. Watch Total Blocking Time. Reflow time inside long tasks counts toward it.

What mistakes should I avoid?


Audit your URL at https://lighthouse-md.com.

Audit your page now

Paste your URL, get scores plus a CLAUDE.md plan for Claude Code.

Run audit →
Add us as a preferred source on Google

One click, and Google shows more of lighthouse-md.com in your search results.