Skip to main content
Markets8 min readUpdated

Real-Time Trading Dashboard Architecture: Streaming Updates, Chart Performance, and State That Never Tears

How to build a browser dashboard that renders live prices, order books, and portfolio P&L at high update rates without dropping frames — transport choice, update batching, canvas rendering, and consistent state under reconnects.

Summary

A trading UI fails in a specific way: it works fine in testing and dies during the exact market conditions users care about, because update rates spike an order of magnitude the moment volatility arrives. The fix is architectural — decouple the network update rate from the render rate, keep high-frequency data out of React state entirely, render dense visuals on canvas, and treat reconnection as a full resynchronization rather than a resume.

Next.jsTypeScriptReactZustandCanvasWebSocket

Never render at the update rate

A liquid symbol can produce hundreds of updates per second, and an order book far more. Calling setState on each one queues hundreds of renders per second against a display that refreshes sixty times. The browser cannot win that race, and the interface becomes unresponsive precisely when a user needs to act.

Decouple them. Writes go into a plain mutable buffer at full speed; a single animation frame loop flushes the accumulated state into React once per frame. Update rate becomes irrelevant to render cost.

// Hot path: no React, no allocation, no re-render.
const latest = new Map<string, Quote>();
let dirty = false;

socket.onmessage = (e) => {
  const q = decode(e.data);
  latest.set(q.symbol, q);   // last write wins — stale ticks are worthless
  dirty = true;
};

// Cold path: one commit per frame, only for symbols actually on screen.
function frame() {
  if (dirty) {
    useQuotes.setState({ quotes: new Map(latest) });
    dirty = false;
  }
  requestAnimationFrame(frame);
}
requestAnimationFrame(frame);

Last-write-wins is the right semantic for quotes: nobody needs the price from 40 milliseconds ago. It is the wrong semantic for trades and fills, which must be queued and processed in full — separate the two streams rather than applying one policy to both.

Subscribe to what is visible

  • Subscribe per visible component and unsubscribe on unmount; a watchlist scrolled out of view should not be consuming bandwidth.
  • Select narrowly from the store — a component reading one symbol must not re-render when a different symbol ticks. In Zustand this means a selector returning a primitive, not an object.
  • Throttle differently by surface: a headline price at 60fps, a P&L summary at 4Hz, a positions table at 1Hz. Users cannot read faster than that, and each reduction is free performance.
  • Pause or downgrade streams when the tab is hidden, and resynchronize on visibilitychange rather than replaying a backlog nobody saw.

Canvas for anything dense

DOM nodes are the wrong primitive for a depth chart or a candlestick series. A 500-row order book as DOM elements means 500 layout and paint operations per update; the same book on a canvas is one draw call against a typed array.

  • Canvas or WebGL for charts, depth visualizations, and heatmaps; DOM only for controls, labels, and anything that must be selectable or accessible.
  • Store series data in typed arrays and append in place — rebuilding an array of objects every frame is where the garbage collector pauses come from.
  • Downsample before drawing: never plot more points than the chart has pixels of width. Min-max decimation per pixel column preserves the visual shape of spikes that naive sampling erases.
  • Scale the canvas by devicePixelRatio explicitly, or every chart is blurry on the retina display your users have.
  • Keep a DOM-based accessible summary alongside the canvas — a table of the current values — so the interface is not opaque to screen readers.

Reconnection is resynchronization

The dangerous state is not a disconnected dashboard — users notice that. It is a reconnected dashboard showing stale positions with confident-looking numbers. After any gap, assume everything is wrong.

  1. On disconnect, immediately mark affected data as stale in the UI — dim it, badge it, stop animating it. Never leave a number looking live when it is not.
  2. Reconnect with exponential backoff and jitter so a venue outage does not turn every client into a thundering herd on recovery.
  3. On reconnect, fetch a full snapshot before applying any incremental update, and discard buffered deltas older than the snapshot's sequence.
  4. For order books, apply deltas only when sequence numbers are contiguous with the snapshot; on any gap, throw the book away and re-snapshot. A book with a hole in it is worse than no book.
  5. Reconcile positions and balances against the server on every reconnect. Locally derived P&L is a display convenience, never the source of truth.

One rule that has saved real money: never let the client compute a number that a user might act on financially without the server agreeing. Display optimistic values if you must, but mark them, and replace them with authoritative values as soon as they arrive.

Frequently asked questions

WebSocket, SSE, or polling for live market data?
WebSocket when the client also sends messages — subscriptions, order entry — which is most trading interfaces. Server-Sent Events are simpler, reconnect natively, and are a good fit for one-way price streams. Polling is defensible only for slow-changing data such as daily portfolio summaries, where the operational simplicity is worth more than the latency.
Why does my React dashboard freeze during volatile markets?
Almost always because state updates are driven at the network's rate rather than the display's. Buffer incoming messages outside React and commit once per animation frame, and make sure components select narrowly so a single symbol's tick does not re-render an entire grid.
Should charts be built from scratch or with a library?
Use a canvas-based charting library for standard price and volume visualizations — the decimation, panning, and axis logic represent a lot of solved work. Build custom when you need a visualization the library does not have, such as a live depth surface or a bespoke execution overlay, and build it on canvas from the start.

Building something like this?

I'm Harsh Mittal — I build production systems across Web3, AI, and financial infrastructure: smart contracts and DeFi protocols, RAG pipelines and LLM agents, market data infrastructure, and the interfaces on top of them. If this is the kind of problem you're working on, I can help you ship it.