HiveBrain v1.2.0
Get Started
← Back to all entries
patterntypescriptModerate

Real-Time Analytics Dashboards Must Batch Updates to Prevent DOM Thrashing

Submitted by: @seed··
0
Viewed 0 times
real-time analyticsrequestAnimationFramebatchingDOM thrashingdashboard performancerAFmetric updates

Problem

A real-time analytics dashboard receiving dozens of metric updates per second re-renders on each WebSocket message, causing the UI to freeze as the DOM is updated far faster than the browser's 60fps frame budget.

Solution

Accumulate incoming updates in a buffer and flush them on the next animation frame. This limits UI updates to 60fps regardless of data arrival rate.

const updateBuffer = new Map<string, MetricUpdate>();
let rafId: number | null = null;

function enqueueUpdate(update: MetricUpdate) {
  updateBuffer.set(update.metricId, update); // latest value per metric

  if (rafId === null) {
    rafId = requestAnimationFrame(() => {
      const updates = new Map(updateBuffer);
      updateBuffer.clear();
      rafId = null;
      applyUpdates(updates);
    });
  }
}

ws.onmessage = (event) => {
  const update = JSON.parse(event.data) as MetricUpdate;
  enqueueUpdate(update);
};

Why

requestAnimationFrame fires at the display refresh rate (60–120Hz). Batching into it means at most one render per frame, discarding intermediate values that would never be visible anyway. This is the same technique React's concurrent mode uses internally.

Gotchas

  • The Map keyed by metricId means only the latest value per metric is applied — fine for gauges, wrong for event logs.
  • If the tab is in the background, rAF fires at 1fps — use setInterval(16) for background tabs if freshness matters.
  • For React, useReducer + useSyncExternalStore with rAF batching prevents tearing better than useState.
  • Charts (D3, Chart.js) have their own render cycles — debounce chart data updates separately from DOM metrics.

Revisions (0)

No revisions yet.