AI Won't Replace Frontend Engineers. It'll Make the Best Ones Untouchable.Performance
The specific techniques I used at Sage — virtualization, selective re-renders, and a total rethink of how we structured data loading across a platform serving over ten thousand restaurant managers.
Alex Gromov
Senior Frontend Engineer

The Problem
When I joined the Sage team, the manager dashboard was already serving over 10,000 restaurants. It was also taking an average of 7.2 seconds to load on a standard connection. For a platform where every second of delay means slower order processing — and real money lost — that wasn't acceptable.
The symptoms were obvious. The root causes weren't. Before touching a single line of code, I spent two weeks just watching. Profiling, measuring, talking to users. Here's what I found.
7.2s1.6s- Load Time
2m 30s50s- Order Processing
1870- Test Coverage
Diagnosing the Bottleneck
The dashboard was rendering a table of all active orders — sometimes 400+ rows — every time anything changed. Each order card was a moderately complex component. The math was brutal: 400 items × render cost × state updates every 15 seconds via WebSocket.
The fastest code is code that doesn't run. We had a lot of code running that didn't need to.
Three distinct problems emerged from profiling:
- The entire order list re-rendered on every WebSocket event, even when only one order changed
- All 400+ orders were in the DOM simultaneously, regardless of scroll position
- Initial data load was sequential — orders, then statuses, then courier locations, one after another
Fix #1: Virtualization
Putting 400 complex DOM nodes on screen simultaneously was the single biggest win. We switched to react-window with a custom variable-height implementation. Only the visible rows (plus a small overscan buffer) exist in the DOM at any time.
<AutoSizer>
{({ height, width }) => (
<VariableSizeList
height={height}
width={width}
itemCount={orders.length}
itemSize={getItemSize}
overscanCount={3}
>
{({ index, style }) => (
<OrderRow
order={orders[index]}
style={style}
/>
)}
</VariableSizeList>
)}
</AutoSizer>Result: DOM node count dropped from ~4,800 to ~150. Initial render time halved immediately.
Fix #2: Selective Re-renders
Even with virtualization, every WebSocket update was still causing the entire visible list to re-render. The issue was our Redux state structure — updates to any order were replacing the entire orders array reference, making React think everything had changed.
We normalized the state (orders keyed by ID), wrapped row components in React.memo, and added a custom comparison function:
// Before: orders array replaced entirely
dispatch(setOrders(newOrdersArray));
// After: update only the changed order by ID
dispatch(updateOrder({ id: order.id, changes: updatedFields }));
// Row component only re-renders when its specific order changes
const OrderRow = React.memo(
({ order, style }: OrderRowProps) => { /* ... */ },
(prev, next) =>
prev.order.status === next.order.status &&
prev.order.updatedAt === next.order.updatedAt
);Fix #3: Parallel Data Loading
The initial load was sequential by accident — nobody designed it that way, it just evolved. Orders loaded, then a useEffect triggered statuses, then another triggered courier locations. Three sequential round trips.
// Before: three sequential queries (waterfalling)
const { data: orders } = useQuery(['orders'], fetchOrders);
const { data: statuses } = useQuery(
['statuses'],
fetchStatuses,
{ enabled: !!orders } // waits for orders
);
// After: all three launch in parallel
const [ordersQuery, statusesQuery, couriersQuery] =
useQueries({
queries: [
{ queryKey: ['orders'], queryFn: fetchOrders },
{ queryKey: ['statuses'], queryFn: fetchStatuses },
{ queryKey: ['couriers'], queryFn: fetchCouriers },
],
});The Results
After three weeks of focused work, the numbers came back:
- Average load time: 7.2s → 1.6s (78% reduction)
- Order processing time: 2m 30s → 50s (down 67%)
- DOM node count: ~4,800 → ~150 per render
- WebSocket update cost: 400 re-renders → 1
The broader lesson: performance problems in complex UIs are almost never one thing. They compound. Fix one layer and you expose the next. The trick is to measure precisely, fix methodically, and never optimise what you haven't profiled.
Three weeks. Three techniques. A platform that went from frustrating to fast — and users who noticed within days.