Building a Real-Time Dashboard with React and WebSockets
When we built the Meridian Analytics dashboard, the core requirement was simple: campaign data should update in real-time without the user refreshing the page. Here's how we architected it.
Architecture Overview
The dashboard has three layers: a React frontend with Chart.js for visualization, a Supabase backend for data storage and auth, and Supabase Realtime (built on WebSockets) for live updates.
Client (React + Chart.js)
↕ WebSocket connection
Supabase Realtime
↕ Postgres changes
Supabase Database (PostgreSQL)
↑ ETL pipeline (every 5 min)
Marketing Platform APIs Why Supabase Realtime Over Raw WebSockets
We considered building a custom WebSocket server with Socket.io, but Supabase Realtime gave us two key advantages: automatic reconnection handling and row-level security. When a new campaign metric is inserted into PostgreSQL, Supabase broadcasts the change to all subscribed clients — no custom pub/sub infrastructure needed.
Performance Optimizations
With 12 chart types and thousands of data points, rendering performance was critical. Our approach:
- Virtualized rendering: Only charts in the viewport are rendered. Off-screen charts use placeholder divs.
- Incremental updates: Instead of re-fetching all data on each WebSocket event, we append new points to existing chart datasets.
- Debounced re-renders: Rapid-fire updates are batched into 500ms windows to prevent layout thrashing.
- Web Workers: Heavy data transformations (aggregations, rolling averages) run off the main thread.
Results
The final dashboard loads in under 400ms, handles 12 simultaneous chart updates, and maintains 60fps scrolling even on mid-range devices. The client's team went from spending 2 hours compiling daily reports to checking a single real-time dashboard.
Key Takeaways
- Use managed WebSocket services (Supabase, Ably, Pusher) unless you need custom protocol handling
- Batch rapid updates — your charts don't need 60 updates per second
- Profile rendering performance early, not after the client demos
- Incremental data updates beat full re-fetches every time