Scaling Dashboards with React and D3.js
Patterns I've used to render thousands of live data points in React without dropping frames, from virtualized lists to canvas overlays.
Our dashboard was fine for eight months. Polite feedback, nobody complained about speed. Then a fleet customer switched on their 18,000 routers in a single afternoon, and the time-series chart started taking around 400ms per tick. Scrolling locked the tab. I shipped a hotfix at 11pm that capped the view at 2,000 rows, and that bought us a week to actually fix the thing.
The bottleneck wasn't D3
D3 is embarrassingly fast. It's math and a tight render loop. The bottleneck was me — I'd been letting React re-render every circle on every update, because I thought memoizing the incoming data was enough. It wasn't. Every new batch of telemetry walked the whole virtual DOM and produced thousands of path mutations, and the browser couldn't keep up.
I learned this the slightly embarrassing way, with the Performance panel open and my coffee getting cold. The flame graph had one screaming column of React work per frame. The D3 scale calls? Barely a pixel of time.
What moved the needle
First I split the chart into two layers. The axes, legend, and tooltip stayed in React + SVG because I wanted them crisp and accessible. The dense data layer moved onto a canvas element sized by a ResizeObserver. That alone took us from ~400ms back down to something like 18ms.
const ctx = canvas.getContext('2d')!
function draw(points: Point[]) {
ctx.clearRect(0, 0, width, height)
ctx.fillStyle = '#00ADB5'
for (const p of points) {
ctx.fillRect(x(p.t) | 0, y(p.v) | 0, 2, 2)
}
}The | 0 is a cheap floor — it stops canvas anti-aliasing sub-pixel rectangles, which was chewing up another millisecond on the 20k-point view. Small thing, surprisingly visible once we were in that territory.
A few things I'd tell past-me
- Throttle on the CPU side, not the data side. Users would rather see slightly stale data than a frozen tab.
- Profile on the worst laptop in the office. We had one ancient Chromebook that saved more launches than I can count.
- Don't memoize what you can avoid creating in the first place. The best useMemo is the one you never wrote.
- SVG is for UI. Canvas is for data. Don't mix them until you have to.
The fastest chart is the one you didn't have to draw.
These days when someone asks for a new chart, my first question is what the worst-case dataset actually looks like. I used to build for the happy path and hope. Now I assume someone is going to point a firehose at it on day one.