Case study 01  //  Fintech  //  6 weeks  //  2025

Neon Ledger

A trading dashboard that took 6.2 seconds to become usable. It now takes 0.8, and nobody rewrote it.

ASCII rendering of the Neon Ledger dashboard: layered waveform bands drawn in green characters against black.
0.8sTime to interactive
−71%JavaScript shipped
1.1sLCP, 75th pct field
+18%Daily active traders

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

  1. 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.
  2. 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.
  3. 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.
  4. 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.
fig. 02 — time to interactive, before / after
  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.”

— Marta Oyelaran, VP Engineering, Neon Ledger

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.

Start a project Call