# Excessive DOM size in Next.js

> How to fix excessive dom size in Next.js — 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 Next.js

Same virtualization applies, but also move list rendering into Server Components and stream it — the client bundle and hydration cost drop, and you can paginate at the data layer instead of shipping every row.

_Paginate on the server instead of rendering all rows_

```tsx
export default async function Page({ searchParams }: { searchParams: Promise<{ page?: string }> }) {
  const { page = '1' } = await searchParams
  const rows = await db.items.findMany({ take: 50, skip: (Number(page) - 1) * 50 })
  return <List rows={rows} />
}
```

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