February 28, 2026

Optimizing React Rendering at Scale

How we reduced time-to-interactive and fixed input lag in a growing React 19 product.

Optimizing React Rendering at Scale cover image

Performance work gets framed as a React problem far too often. In practice, most slow interfaces come from coordination problems: too much work happening on the client, too much state changing at once, and too little clarity around which updates are actually urgent to the person holding the device.

The app we were tuning had grown into a dense operating surface with live filters, large result sets, background hydration, and multiple views competing for the same render budget. The goal was not to make every component mathematically optimal. The goal was to make the product feel immediate.

Measure interaction pressure, not render counts

Counting renders is useful for debugging, but it is a weak product metric. A component can render often and still feel fast. A screen can render only a handful of times and still feel broken if the wrong work blocks a search field, a filter tap, or the first paint of a dense list.

We started by tracing moments that users actually notice: keypress latency, filter response time, visible list stabilization, and the delay between route change and meaningful content. That reframed the work immediately. The biggest regressions were not isolated in a single component tree. They were caused by several medium-cost updates landing together.

Separate urgent updates from expensive work

React 19 gives you a cleaner vocabulary for urgency. We used transitions to mark non-blocking updates, deferred values to keep high-frequency inputs responsive, and tighter ownership boundaries so view recomposition did not spill into unrelated panels.

That changed the rhythm of the app. Search input stayed immediate while filtering, sorting, and secondary summaries caught up a beat later. Users rarely object to staged updates when the interface makes the sequence legible. They do object when typing feels sticky.

Stabilize the surface area before memoizing it

Memoization is not a strategy. It is a tax you take on when the data flow is already stable. Before adding any memo-related fixes, we flattened prop churn, normalized object creation at the edges, and removed state that existed only to echo other state.

That alone eliminated a large share of waste. Once the surface area stopped shifting on every render, the remaining hotspots became obvious and far fewer targeted optimizations were needed. The result was simpler than the defensive useMemo and useCallback blanket we had originally considered.

Move predictable work to the server

A surprising amount of client work was simply assembly work: shaping data for cards, merging duplicate fetch paths, and preparing summaries that did not need to wait for browser execution. Server components let us move those predictable transforms closer to the data and ship a thinner client tree.

The key was being strict about ownership. If a concern needed live client state, we kept it client-side. If it was deterministic and view-driven, we pulled it back to the server. That avoided the common trap of creating blurry boundaries that are hard to reason about later.

Let the interface reveal work in stages

Performance is partly a systems problem and partly a presentation problem. We redesigned loading and update states so the interface always acknowledged progress. Primary content resolved first. Supporting summaries, charts, and secondary controls filled in after. That made the app feel calmer even before the underlying trace improved.

This also forced better product discipline. If a panel could appear later without hurting comprehension, it probably should not block the first useful frame. Treating hierarchy as a performance tool gave us cleaner layouts and faster perceived speed at the same time.

The operating rules we kept

After the refactor, we wrote down a handful of rules and used them as review criteria. The point was to stop the same regressions from returning six weeks later under deadline pressure.

Author

Arjun Bishnoi