# Excessive DOM size in React

> How to fix excessive dom size in React — with the exact fix and copy-paste code.

_Category: Performance · Detector `dom-bloat` · Severity: error_

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.

## 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.

```tsx
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>
  )
}
```

[React docs](https://tanstack.com/virtual/latest)

### Steps

1. Find the largest list or repeated subtree (VibeCheck reports the heaviest selector)
2. Virtualize it so only visible rows are in the DOM
3. Remove redundant wrapper elements and replace `display:none` with conditional rendering

## How VibeCheck detects it

The `dom-bloat` detector flags this live in the browser and reports it to the widget's Problems list — and to your coding agent over MCP.

- **Issue string:** `DOM has 1500 nodes`
- **Threshold:** ≥ 800 nodes (warning), ≥ 1,500 nodes (error) — sampled every 5s

## 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:none` reduce 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-visibility` fix 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.

See the general, framework-agnostic fix: https://vibecheck.wcgw.fun/fix/excessive-dom-size.md

---

Fix guide from VibeCheck — https://vibecheck.wcgw.fun/fix/excessive-dom-size. Full site index for LLMs: https://vibecheck.wcgw.fun/llms.txt
