Neon Ledger
A trading dashboard that took 6.2 seconds to become usable. It now takes 0.8, and nobody rewrote it.
01 // The brief
Traders were using a competitor's terminal to check prices
Neon Ledger is a portfolio dashboard for small commodity desks. It worked. The numbers were right, the charts were good, and the support inbox was quiet. But usage fell off a cliff between 08:55 and 09:05 every weekday — exactly the ten minutes when a trader most needs to see a position.
The team assumed they needed a redesign. Session replays said otherwise: people were opening the app, waiting, alt-tabbing to a competitor's terminal, and coming back a minute later. The interface was fine. It was just late.
02 // The constraint
No rewrite, no downtime, six weeks
A rewrite was off the table — the app is regulated, and re-certifying a new stack would have taken longer than the fix. Everything had to land incrementally, behind flags, on the existing React codebase, while the team kept shipping features.
This is the normal case, not the hard one. Most performance work is archaeology plus discipline, not a greenfield.
03 // What shipped
Four changes, in order of payoff
- Deferred the charting library. 480 KB of chart code was in the entry bundle, on every route, including login. Moved behind a dynamic import triggered by the chart panel entering the viewport.
- Streamed the position table. The app waited for one aggregate endpoint returning 40 000 rows before painting anything. It now streams the first 50 rows and hydrates the rest behind them.
- Removed three render-blocking scripts. Two analytics tags and a font loader nobody could account for. One had been added in 2021 for a campaign that ended in 2021.
- Put a budget in CI. A Lighthouse-CI gate fails the build if the entry bundle exceeds 180 KB or LCP regresses past 1.4s on the reference profile. This is the change that made the other three permanent.
before ████████████████████████████████████████████ 6.2s
week 2 ███████████████████████ 3.1s
week 4 ██████████ 1.4s
after █████ 0.8s
| | | | |
0s 1.5s 3.0s 4.5s 6.0s
04 // The result
The 09:00 cliff flattened
Six weeks after launch, daily active traders were up 18% with no marketing change, and the morning usage dip disappeared from the analytics entirely. Field LCP at the 75th percentile settled at 1.1 seconds. The performance budget has failed the build eleven times since — which is the point.
client“Our dashboard went from the thing customers complained about to the thing they mention in sales calls. Dmitrii found four render-blocking scripts we had all stopped seeing.”
05 // What I would do differently
I put the CI budget in last. It should have been first.
For the first three weeks the team shipped two regressions on top of my fixes, because nothing stopped them. Had the budget landed in week one, the graph above would have been a straight line down instead of a sawtooth. That is now step one on every performance engagement.
Is your app late too?
A performance teardown takes two weeks and starts with real field data from your users, not a lab score.
Reply within 4 business hours, guaranteed.