All guides · Performance

DOM Size and Forced Reflows: Keep Layout Work Cheap

Every element in the HTML becomes a node in the DOM, the browser’s in-memory model of the page. For each node the browser works out which CSS rules apply, where it sits on the page and what it looks like. The more nodes, the more work every style change, resize and interaction costs.

Why it matters

A large DOM makes the first render slower and keeps costing afterwards: opening a menu or adding a class to the body can make the browser restyle and re-lay out thousands of elements. That shows up as main-thread time, Total Blocking Time in the lab and slower Interaction to Next Paint for real visitors. It also uses more memory, which matters on low-end phones.

Where large DOMs come from

  • Page builders that wrap each piece of content in several layers of divs for sections, rows, columns and widgets.
  • Hidden duplicates: a separate mobile menu, a mega menu and an off-canvas menu all present in the HTML at once, or the same content rendered twice for desktop and mobile and hidden with CSS.
  • Long lists rendered all at once: hundreds of products, comments or table rows.
  • Inline SVG icons repeated many times, each with its own paths.

How to fix a large DOM

  • Simplify layouts. Use CSS grid and flexbox instead of nested wrapper elements, and remove empty or decorative containers that page builders leave behind.
  • Render navigation once and restyle it for small screens with media queries, instead of shipping two or three copies.
  • Paginate or load long lists in pages (“load more”). For very long lists in web apps, virtual scrolling keeps only the visible rows in the DOM.
  • Reuse repeated SVG icons with <use> referencing one symbol.
  • For long pages, content-visibility: auto lets the browser skip rendering work for sections that are off-screen until they come close. Give them an estimated size so the scrollbar doesn’t jump:
.article-section {
  content-visibility: auto;
  contain-intrinsic-size: auto 800px;
}

On WordPress, compare the element count of a page built with a page builder against the same content in the block editor; switching templates or turning off unused widgets often makes a large difference.

Forced reflows

Normally the browser batches layout: scripts change styles, and the browser recalculates layout once before the next frame. A forced reflow happens when a script changes something and then immediately reads a layout property such as offsetHeight, offsetTop or getBoundingClientRect(). To answer, the browser must recalculate layout right away. Doing this in a loop, alternating writes and reads, is called layout thrashing:

// Slow: each read forces layout after the previous write
for (const card of cards) {
  card.style.width = container.offsetWidth / 3 + 'px';
}

// Better: read once, then write
const width = container.offsetWidth / 3;
for (const card of cards) {
  card.style.width = width + 'px';
}

Batch all reads before all writes. Work that changes the page visually can go into requestAnimationFrame, so it runs just before the next frame. Often the cleanest fix is to let CSS do the job, for example with grid or flexbox, instead of measuring in JavaScript.

How PerfoMetrix checks this

Both checks come from the Lighthouse report of the PageSpeed Insights run we already make (mobile, simulated device).

  • DOM size (dom-size-insight): we rate the total number of elements ourselves.
Result Total elements
Passed 1,500 or fewer
Needs attention 1,501 to 3,000
Failed more than 3,000
  • Forced reflow (forced-reflow-insight): if Lighthouse rates it 0.9 or higher, or it doesn’t apply, the check passes; otherwise it needs attention, and we show the first five items it lists, usually the script locations that triggered the reflows.

These checks are shown but don’t count toward the performance score: their effect is already measured by TBT and the other scored metrics. No extra requests are made to your site.

Learn more

Last updated October 28, 2026