Engineering Dec 02, 2025 8 min read

Why I replaced Redux with
raw WebSockets.

Justin Wang
High-Performance Interface Designer

We have been taught that the client-side store is the Single Source of Truth. We serialize data into Redux or Zustand, diff the Virtual DOM, and reconcile changes. For 99% of apps, this is perfect.

But when I was building the Vault Dashboard, dealing with 5,000+ market ticks per second, this architecture fell apart. The browser wasn't struggling to render; it was struggling to allow Redux to decide what to render.

Traditional Flow
JSON Parse
Dispatch
Re-Render
~85ms Latency
Stream Architecture
Buffer
Direct Paint
~4ms Latency

Latency impact of State Management overhead at 1000Hz

The "Store" is the Bottleneck

In a high-frequency trading context, the data on the screen is stale the moment it is rendered. Validating that data against a client-side schema, normalizing it for a Redux slice, and then triggering selectors creates a massive backlog on the main thread.

We realized that for the order book and the tick chart, the client does not need to "know" the state. It just needs to display it. We don't need to store the tick history in Redux if we are just painting it to a Canvas.

Bypassing the V-DOM

We switched to a raw WebSocket implementation using `ArrayBuffers` (Protobufs) instead of JSON strings. This reduced the payload size by 60%, but the real win was bypassing React completely for high-frequency components.

const useSocketStream = (canvasRef) => {
  useEffect(() => {
    const ws = new WebSocket('wss://api.vault.com/stream');
    ws.binaryType = 'arraybuffer';

    ws.onmessage = (event) => {
      // Decode directly in the worker or main thread
      const view = new DataView(event.data);
      const price = view.getFloat32(0);
      
      // DIRECT PAINT: No state update, no re-render cycle
      paintChart(canvasRef.current, price);
    };

    return () => ws.close();
  }, []);
};

By treating the visual layer as a "dumb terminal" for the server's state, we eliminated the Garbage Collection pauses caused by thousands of temporary Redux action objects being created and destroyed every second.

When to ignore this advice

If you are building a Todo app, a dashboard with modest updates, or essentially anything that isn't a real-time game or financial instrument, please use Redux (or Context, or Zustand).

This architecture sacrifices "Predictability" and "Time Travel Debugging" for raw speed. In our case, predictability didn't matter if the user was seeing prices that were 200ms old.

The Takeaway

Don't let "Best Practices" dictate your architecture when you are solving non-standard problems. The browser is incredibly fast if you get out of its way.

Read Next