How would you avoid rendering offscreen DOM nodes
Don't keep what the user can't see in the DOM. Virtualize long lists (render only the visible window + buffer), lazy-mount below-the-fold sections, use content-visibility: auto, and defer offscreen images with loading=lazy.
Offscreen DOM nodes cost memory, layout, paint, and (in React) reconciliation — for content nobody is looking at. The goal: the DOM size tracks what's visible, not what exists in your data.
1. Virtualize long lists
For thousands of rows, render only the visible window plus a small overscan buffer, absolutely positioned inside a spacer element that has the full scroll height.
- Libraries:
react-window,react-virtual/@tanstack/react-virtual,virtua. - Fixed-height rows are trivial; dynamic heights need measurement + a position cache.
- Keeps DOM at ~20–50 nodes regardless of dataset size.
2. content-visibility: auto
.section { content-visibility: auto; contain-intrinsic-size: 0 500px; }The browser skips layout and paint for offscreen sections until they scroll near the viewport. contain-intrinsic-size provides a placeholder size so the scrollbar stays sane. Almost free, no JS.
3. Lazy-mount below-the-fold regions
Use an IntersectionObserver to mount heavy sections (comments, related items, maps, charts) only when they approach the viewport. In React, conditionally render once the sentinel intersects.
4. Defer offscreen media
<img loading="lazy">and<iframe loading="lazy">.IntersectionObserverfor anything custom (videos, canvas, third-party widgets).
5. Unmount, don't just hide
display: none keeps the node, its memory, and its event listeners. For truly far-away content, remove it from the tree and re-add on demand.
How to decide
- Profile first: Lighthouse flags "Avoid an excessive DOM size" (>~1500 nodes, depth >32).
- Long homogeneous lists → virtualization.
- Long heterogeneous page →
content-visibility+ lazy-mount sections. - Always measure DOM node count before/after.
Follow-up questions
- •How does virtualization handle variable row heights?
- •What's the difference between content-visibility: auto and lazy-mounting?
- •Why is display:none not enough?
- •How do you keep accessibility (find-in-page, screen readers) working with virtualized content?
Common mistakes
- •Using display:none and assuming the cost is gone — the node, listeners, and memory remain.
- •Virtualizing a list that's only 30 items — added complexity for no gain.
- •Breaking Ctrl+F / screen reader navigation because content isn't in the DOM.
- •Forgetting overscan, causing blank flashes on fast scroll.
Performance considerations
- •DOM size drives memory, style recalc, layout, and paint cost. Virtualization caps node count; content-visibility skips rendering work entirely for offscreen sections. The tradeoff is complexity and accessibility — measure that the win is real before adding it.
Edge cases
- •Anchor links / scroll-to-item into not-yet-rendered virtualized rows.
- •Variable and unknown-until-rendered row heights.
- •Sticky headers and grouped lists inside a virtualized container.
- •SEO — virtualized content isn't in the initial HTML.
Real-world examples
- •An infinite feed (Twitter/X timeline) virtualizing thousands of posts down to ~30 DOM nodes.
- •A long settings or docs page using content-visibility: auto per section.
- •A data grid with 100k rows staying responsive via windowed rendering.