← Docs

Recipe: Virtualized long list rendering

Render 100k rows without frame drops using intersection observers and DOM recycling.

Problem

Rendering thousands of DOM nodes blocks the main thread, causing jank and memory pressure. Users scrolling a table or feed experience visible lag.

Approach

  • Measure a fixed row height (e.g. 48px).
  • Calculate visible range from scroll offset + container height.
  • Render only visible rows plus a small overscan buffer.
  • Use absolute positioning with a spacer div for total height.
  • Recycle DOM nodes — never unmount/mount on every scroll tick.

Core loop

const range = {
  start: Math.floor(scrollTop / ROW_H),
  end: Math.ceil((scrollTop + viewH) / ROW_H)
};
const offsetY = range.start * ROW_H;
// Render items[range.start..range.end] at transform: translateY(offsetY)

Caveats

  • Variable-height rows require a measured cache.
  • Use will-change: transform sparingly.
  • Pair with content-visibility: auto for free browser-level skipping.