← Back

Virtualization for Large Lists: Rendering 10,000+ Annotations Without Lag

·frontend-explore

Virtualization for Large Lists: Rendering 10,000+ Annotations Without Lag

Key Takeaway

Our annotation list rendered all 10,000+ items in the DOM simultaneously, causing severe performance issues. Implementing react-window for virtualization reduced DOM nodes from 10,000 to ~20 and improved scroll performance from 5 FPS to 60 FPS.

The Problem

function AnnotationList({ annotations }: { annotations: Annotation[] }) {
  // Renders ALL 10,000 items! 🔥
  return (
    <div>
      {annotations.map(annotation => (
        <AnnotationItem key={annotation.id} annotation={annotation} />
      ))}
    </div>
  );
}

Issues: 5 FPS scrolling, 15-second initial render, 45MB DOM size, browser freezes, mobile crashes.

The Solution

import { FixedSizeList } from 'react-window';

function AnnotationList({ annotations }: { annotations: Annotation[] }) {
  const Row = ({ index, style }: { index: number; style: React.CSSProperties }) => (
    <div style={style}>
      <AnnotationItem annotation={annotations[index]} />
    </div>
  );

  return (
    <FixedSizeList
      height={600}  // Viewport height
      itemCount={annotations.length}  // 10,000
      itemSize={80}  // Row height
      width="100%"
    >
      {Row}
    </FixedSizeList>
  );
}
// Only renders ~20 visible items!

Variable-size items:

import { VariableSizeList } from 'react-window';

function VariableAnnotationList({ annotations }: Props) {
  const listRef = useRef<VariableSizeList>(null);

  const getItemSize = (index: number) => {
    const annotation = annotations[index];
    return annotation.type === 'detailed' ? 120 : 60;
  };

  return (
    <VariableSizeList
      ref={listRef}
      height={600}
      itemCount={annotations.length}
      itemSize={getItemSize}
      width="100%"
    >
      {Row}
    </VariableSizeList>
  );
}

Impact

| Metric | Before | After | |--------|--------|-------| | DOM nodes | 10,000 | 20 | | Initial render | 15s | 0.2s | | Scroll FPS | 5 | 60 | | Memory usage | 450MB | 45MB |

Lessons Learned

  1. Virtualize Long Lists: Only render visible items
  2. react-window is Lightweight: Better than react-virtualized for most cases
  3. Fixed Size is Fastest: Use when possible
  4. Cache Item Sizes: For variable-size lists
  5. Windowing Threshold: Virtualize at 100+ items