>soren hale — frontend developer
// Case study
Kestrel Pay Dashboard
Live payment data on a table of 200,000 rows that still answers a click at once.
INP on the transactions tablefrom 380 ms→to 48 ms
- Client
- Kestrel Pay
- Year
- 2024
- Role
- Senior frontend developer
- Kind
- Product

The problem
Merchants keep the Kestrel dashboard open all day. Every new payment re-rendered the whole table, and by the afternoon a filter click took a third of a second to answer.
The approach
- Rows are virtualised and keyed by payment id; a live update touches one row, never the list.
- Filters run in a worker and return in chunks, so typing never waits for a sort.
- The socket batches updates per frame instead of per message.


The result
Interaction to Next Paint on the busiest view fell from 380 ms to 48 ms at the 75th percentile. Memory after eight hours open is flat.
Built with
ReactTypeScriptTanStack VirtualWebSocket
// Case study
Kestrel Pay Dashboard
Live payment data on a table of 200,000 rows that still answers a click at once.
INP on the transactions tablefrom 380 ms→to 48 ms
- Client
- Kestrel Pay
- Year
- 2024
- Role
- Senior frontend developer
- Kind
- Product

The problem
Merchants keep the Kestrel dashboard open all day. Every new payment re-rendered the whole table, and by the afternoon a filter click took a third of a second to answer.
The approach
- Rows are virtualised and keyed by payment id; a live update touches one row, never the list.
- Filters run in a worker and return in chunks, so typing never waits for a sort.
- The socket batches updates per frame instead of per message.


The result
Interaction to Next Paint on the busiest view fell from 380 ms to 48 ms at the 75th percentile. Memory after eight hours open is flat.
Built with
ReactTypeScriptTanStack VirtualWebSocket
// Case study
Kestrel Pay Dashboard
Live payment data on a table of 200,000 rows that still answers a click at once.
INP on the transactions tablefrom 380 ms→to 48 ms
- Client
- Kestrel Pay
- Year
- 2024
- Role
- Senior frontend developer
- Kind
- Product

The problem
Merchants keep the Kestrel dashboard open all day. Every new payment re-rendered the whole table, and by the afternoon a filter click took a third of a second to answer.
The approach
- Rows are virtualised and keyed by payment id; a live update touches one row, never the list.
- Filters run in a worker and return in chunks, so typing never waits for a sort.
- The socket batches updates per frame instead of per message.


The result
Interaction to Next Paint on the busiest view fell from 380 ms to 48 ms at the 75th percentile. Memory after eight hours open is flat.
Built with
ReactTypeScriptTanStack VirtualWebSocket
// Start a project
Tell me what you are building.
Three quick picks so the first reply is useful. You finish the message on the contact page.
// Start a project
Tell me what you are building.
Three quick picks so the first reply is useful. You finish the message on the contact page.
// Start a project
Tell me what you are building.
Three quick picks so the first reply is useful. You finish the message on the contact page.