Excessive DOM size in React
Every DOM node the browser has to style, lay out, and keep in memory costs time on each frame, so a page with thousands of nodes feels sluggish to scroll and slow to interact with. AI agents produce this constantly: they map over data without virtualization, wrap everything in defensive <div>s, and render hidden content with display:none instead of not rendering it. The result passes review because it looks right — it just quietly janks.
Symptoms
How VibeCheck catches it
In your widget · Problems
DOM has 1500 nodes
To your coding agent · MCP
agent › get_detected_issues
→ { detector: "dom-bloat", issue: "DOM has 1500 nodes", threshold: "≥ 800 nodes (warning), ≥ 1,500 nodes (error) — sampled every 5s" }
The same string in your widget and in your agent’s context — no screenshot, no copy-paste.
Root causes
The fix for React
Use a windowing library so a 10,000-row list keeps ~20 rows in the DOM. @tanstack/react-virtual is headless and works with any markup.
import { useVirtualizer } from '@tanstack/react-virtual'
function Rows({ items }: { items: Item[] }) {
const parentRef = useRef<HTMLDivElement>(null)
const rows = useVirtualizer({
count: items.length,
getScrollElement: () => parentRef.current,
estimateSize: () => 40,
})
return (
<div ref={parentRef} style={{ height: 600, overflow: 'auto' }}>
<div style={{ height: rows.getTotalSize(), position: 'relative' }}>
{rows.getVirtualItems().map((v) => (
<div key={v.key} style={{ position: 'absolute', top: v.start, height: v.size }}>
{items[v.index].label}
</div>
))}
</div>
</div>
)
}Steps
- Find the largest list or repeated subtree (VibeCheck reports the heaviest selector)
- Virtualize it so only visible rows are in the DOM
- Remove redundant wrapper elements and replace
display:nonewith conditional rendering
FAQ
- How many DOM nodes is too many?
- Lighthouse warns past ~800 and errors past ~1,500 nodes in one document — the same thresholds VibeCheck uses. Under ~800 is comfortable; the exact number matters less than avoiding unbounded lists.
- Does hiding elements with
display:nonereduce DOM size? - No. Hidden elements are still in the DOM and still cost memory and (for some operations) layout. Conditionally render them instead so they aren’t created until needed.
- Will
content-visibilityfix it on its own? - It skips rendering work for offscreen sections, which is a big, cheap win — but the nodes still exist. For truly large lists (thousands of rows) you still need virtualization.