Excessive DOM Size
Your page has more nested divs than a set of Russian nesting dolls. Here's why that kills performance and how to flatten things out.
What is this?
Think of your HTML like Russian nesting dolls — except someone made 50 layers deep and each one contains a dozen more dolls inside it. That is what happens when your page has an excessive DOM (Document Object Model). Every HTML element on your page is a "node" in the DOM tree, and when you have hundreds or thousands of deeply nested elements, the browser has to work overtime to figure out what to paint on screen.
Why it matters
- For your visitors: Every extra DOM node means more memory usage and slower interactions. Scrolling gets janky, clicks feel sluggish, and on older phones the whole page can freeze. Your visitors feel it even if they cannot name the problem — they just know your site feels "heavy."
- For your business: Google uses page performance as a ranking signal. A bloated DOM tanks your Core Web Vitals scores, especially Interaction to Next Paint (INP). Slower sites mean fewer conversions, higher bounce rates, and lower search rankings.
- The standard: Google recommends keeping your DOM under 1,500 nodes total, with a maximum depth of 32 levels and no parent element with more than 60 child elements. Most sites built with AI tools blow past these numbers without realizing it.
<main>
<article>
<h2>Product Name</h2>
<p>Description goes here.</p>
<button>Add to Cart</button>
</article>
</main><div class="outer">
<div class="container">
<div class="inner">
<div class="wrapper">
<div class="content">
<div class="text-wrapper">
<h2>Product Name</h2>
<p>Description goes here.</p>
</div>
</div>
</div>
</div>
</div>
</div>How to fix it
React / Next.js
The biggest culprit in React is unnecessary wrapper divs. Use Fragments to group elements without adding DOM nodes.
// Before: every component adds a wrapper div
function ProductCard({ name, price }: { name: string; price: number }) {
return (
<div>
{" "}
{/* unnecessary wrapper! */}
<h3>{name}</h3>
<span>${price}</span>
</div>
);
}
// After: Fragment adds zero DOM nodes
function ProductCard({ name, price }: { name: string; price: number }) {
return (
<>
<h3>{name}</h3>
<span>${price}</span>
</>
);
}For long lists, virtualize them so only visible items exist in the DOM:
import { useVirtualizer } from "@tanstack/react-virtual";
function ProductList({ items }: { items: Product[] }) {
const parentRef = useRef<HTMLDivElement>(null);
const virtualizer = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 80,
});
return (
<div ref={parentRef} style={{ height: "400px", overflow: "auto" }}>
<div style={{ height: virtualizer.getTotalSize() }}>
{virtualizer.getVirtualItems().map((vItem) => (
<div key={vItem.key} style={{ height: vItem.size }}>
{items[vItem.index].name}
</div>
))}
</div>
</div>
);
}Plain HTML
Audit your nesting and remove divs that exist only for styling. Use CSS Grid and Flexbox instead of wrapping elements for layout.
<!-- Use semantic elements — they style just as well as divs -->
<section>
<h2>Our Products</h2>
<ul class="product-grid">
<li>Widget A — $10</li>
<li>Widget B — $20</li>
</ul>
</section>
<style>
.product-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
gap: 1rem;
}
</style>A bloated DOM is like fur matted into knots — it slows everything down and looks terrible. The cat insists you flatten that tree.
How the cat scores this
The scanner counts total DOM nodes, measures maximum nesting depth, and checks for parent elements with excessive children. Pages over 1,500 nodes get flagged; pages over 3,000 get a serious penalty. Deep nesting beyond 32 levels is called out specifically because it hammers CSS selector matching and layout recalculation.
Further reading
- web.dev: DOM size — Google's guide to why DOM size matters for performance
- MDN: Document Object Model — understand what the DOM actually is
- TanStack Virtual — the go-to library for virtualizing long lists in React