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: autolets 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.