MOEIN.
← Writing
Updated 2026-08-222 min read

Marketplace UI Under Realtime Load

Lessons from e-B2B trading surfaces — keeping purchase flows calm when supply-chain events never stop arriving.

  • frontend
  • realtime
  • architecture
Abstract live order book with a calm checkout lane beside noisy price ticks.

A trading surface is not a dashboard with a Buy button glued on. Prices, inventory, and counterparty state arrive as a firehose. The purchase path has to feel like a form: one decision, one confirmation, no flicker.

Split the streams

Treat inbound events as at least two products:

  • Signal — prices, depth, presence. High frequency. Lossy is allowed if the latest value wins.
  • Commit — cart, quote lock, payment intent. Low frequency. Must be exact.

Those streams should not share a React context at the top of the tree. Signal can live in a store that components subscribe to by selector. Commit should be props and server state.

type MarketTick = {
  sku: string;
  bid: number;
  ask: number;
  ts: number;
};
 
type CheckoutSnapshot = {
  quoteId: string;
  sku: string;
  lockedAsk: number;
  expiresAt: number;
};

Batch before you render

WebSocket messages are cheaper than frames. If you setState per tick, React will spend the session reconciling work the user cannot perceive.

The pattern that survived production load is a rAF-aligned batcher: collect ticks for the frame, fold by sku, then publish one immutable map.

export function createTickBatcher(publish: (snapshot: Map<string, MarketTick>) => void) {
  const pending = new Map<string, MarketTick>();
  let frame = 0;
 
  return function ingest(tick: MarketTick) {
    pending.set(tick.sku, tick);
    if (frame) return;
 
    frame = requestAnimationFrame(() => {
      publish(new Map(pending));
      pending.clear();
      frame = 0;
    });
  };
}

Latest-write-wins is correct for a book. It is wrong for fills and invoices. Do not reuse this helper on commit events.

Keep checkout off the hot path

The buy flow should read a locked quote, not the live ask. Lock on intent, show a countdown, and refuse to bind the submit button to the tick store.

Fan-out sits in front of checkout so the buy path never re-renders at tick rate.
Fan-out sits in front of checkout so the buy path never re-renders at tick rate.

A practical UI split:

  1. Ticker and depth subscribe to the batcher.
  2. Quote lock is a server action with an expiry.
  3. Toasts and banners are a third lane so errors do not steal layout from the form.

What to measure

If you cannot see these, you are guessing:

  • Time from tick arrival to paint, p95
  • Checkout renders per minute while the book is live
  • Quote lock mismatch rate (UI ask vs server ask at submit)

The architecture is working when the book looks alive and the purchase form looks bored.

Takeaway

Realtime UI is a scheduling problem. Give noise a cheap lane, give money a quiet lane, and never let them share a render.