I was building a live ops board that streams a few dozen metrics over a websocket, each shown as a number plus a rolling sparkline. The naive build re-rendered the entire React tree on every message and dropped frames the moment the feed got busy.
What worked was making the model separate the transport from the view: a ring buffer per metric outside React, a fixed-window downsample, and components that read the buffer on an animation frame rather than on every socket message. It also had to handle reconnect and backfill without a flash of empty charts.
How are you deciding the rolling window and downsample factor? I hardcoded 300 points but it feels arbitrary for metrics with very different rates.
Build a real-time metrics dashboard (React + TypeScript) fed by a websocket emitting {metric: string, t: number, v: number} messages. Design for load:
Transport vs view separation: keep a per-metric ring buffer (fixed capacity, typed array) OUTSIDE React state. Socket messages write to the buffer only. A single requestAnimationFrame loop snapshots buffers and updates the view at most once per frame - never setState per message.
Downsampling: each sparkline shows a fixed window of {WINDOW} seconds; if raw points exceed the pixel width, downsample with Largest-Triangle-Three-Buckets (LTTB) so spikes survive, not naive every-nth. Recompute only when the window's data changed.
Resilience: on disconnect, show a 'reconnecting' state and keep the last good chart dimmed (no empty flash); on reconnect, backfill from the buffer and resume. Detect a stalled feed (no message in 3x the expected interval) and flag that tile.
Each tile: current value (large), delta vs window start (arrow + color, and a text sign for colorblind users), and the sparkline. Grid is responsive. Include a synthetic message generator so it runs standalone. Comment where the hot path is and why nothing there allocates.