Picture the person this running app is for. He's four miles into a Tuesday run, phone pulled out at a red light, and he wants to know his interval targets before the light turns green. He's got maybe ten seconds. In that window the app is either ready or it's making him wait, and if it makes him wait he stops trusting it, one small disappointment at a time. That runner is who I think about when I think about speed. Not a number on a chart. A guy at a red light with ten seconds.
Here's the uncomfortable part. For months I knew the signed-in pages of Pheidi opened to a spinner before they opened to his plan, and I did nothing about it. Not because it was hard. Because it never earned a spot on the roadmap. A new feature demos. You can show it to someone. "The plan now paints a third of a second sooner" demos to nobody. So the speed work sat at the bottom of the list where speed work always sits, and I shipped the slow version and told myself I'd get to it.
I build Pheidi alone. One founder, no team, an adaptive running training app that reshapes your plan around your real life. And what finally got me to fix the slow paths wasn't discipline. It was a pile of AI coding credits I'd bought and wasn't burning through.
Token maxing, and why it unlocked the boring work
There's a phrase going around for this: token maxing. You've got model credits, they're cheaper than your time, so you point them at work you'd never justify at your old hourly rate. Most people reach for it to crank out features. I reached for it to do the opposite, the invisible maintenance that only ever loses to features in a straight fight.
That's the real insight here, and it's got nothing to do with running. Performance work isn't hard, it's unrewarded. Parallelizing a few database queries, moving a slow call off the render path, caching a computed value: none of that is clever. It's just tedious and it doesn't show up in a screenshot, so it never wins the prioritization argument against a shiny new thing. The tedium was always the tax. And tedium is exactly what cheap, AI-assisted code removes. When the effort to do the boring thing drops near zero, the boring thing finally clears the bar.
So for one week I stopped adding features and went hunting for half-seconds. Here's what was hiding.
What the runner was actually paying for
The signed-in pages were the worst of it. When that runner opened his plan, the page painted as the signed-out shell first, a navbar and a spinner, because the app only figured out who he was after its live connection to the server booted. Real content, his actual workout, waited on that boot: connect, negotiate, load the plan, then paint. A one to three second window where the biggest thing on screen was pinned to machinery that had nothing to do with his workout.
The plan load underneath it was doing four database queries one after another, the plan, the profile, the injuries, the alternate plans, when none of them needed to wait for the others. It also blocked first paint on a live weather forecast, an outside call that could take up to three seconds before it gave up. The runner was waiting on a weather server to find out what his intervals were.
And there was a booby trap I only found because a test caught it. When I made the signed-in page hand its already-loaded state to the live connection so it wouldn't load twice, that bundle of state serialized to 117KB. The connection has a silent 32KB ceiling. Cross it and the server just closes the circuit, no error the user can see, the page frozen on the spinner it started with. I'd built a faster path with a trapdoor in it.
What I did
The fixes are unglamorous, which is the point.
The signed-in pages now figure out who you are from your session cookie while the page is still being built on the server, load your plan once, right there, and send back real content in the very first response. The plan is in the HTML before the live connection even starts. When the connection does start, it adopts the work that's already done instead of redoing it, so it's exactly one plan load per page view now, not two racing each other.
Two details worth stealing. First, the plan's four queries now run at the same time instead of in a line, and the weather forecast got kicked off the render path entirely, it applies in the background a beat later and quietly updates the page when it lands. First paint no longer waits on a server in the sky. Second, that 117KB trapdoor: I dropped the redundant computed fields from the payload and ran it through Brotli, which took it from 117KB to 13KB, comfortably under the ceiling, and raised the ceiling too so a long marathon block has headroom. The faster path is now safe by construction.
I also did the quieter housekeeping while I was in there. Static assets get a real cache header now with a version stamp, so a returning runner stops re-downloading the same stylesheet and script every visit but still gets fresh files the instant I deploy. On the marketing site I purged the unused Bootstrap CSS, 31KB of it down to 7.6KB over the wire, and subset Font Awesome from 275KB to 10KB by shipping only the icons I actually use.
The receipts
I ran Lighthouse against the app locally under its simulated Slow 4G profile, published Release build, median of three runs, measuring before and after each change. The barely-moving spread between runs is why I trust these.
| What | Before | After | Change |
|---|---|---|---|
| Landing page LCP (Slow 4G) | 3316 ms | 2711 ms | 18% faster |
| Landing performance score | 90 | 94 | +4 |
| Sign-in page layout shift (CLS) | 0.177 | 0.000 | eliminated |
| Sign-in performance score | 86 | 93 | +7 |
| Plan load, four queries | sequential | parallel | ~100 to 150 ms saved |
| Signed-in circuit handoff payload | 117 KB | 13 KB | 89% smaller |
| Marketing render-blocking Bootstrap | 31 KB gzip | 7.6 KB gzip | 84% removed |
| Font Awesome weight | 275 KB | 10 KB | subset to used icons |
Every one of those is a moment I'm handing back to the guy at the red light. The plan load and the payload shrink are the ones he feels most directly, because they sit right on the path between tapping the icon and seeing his workout.
The win that was really a benchmark fooling me
Here's the part the tidy version of this post would leave out, and it's the whole reason to be careful with cheap effort.
That headline LCP number, 3.3 seconds down to 2.7, was largely a measurement artifact, not the runner's real experience. Pheidi runs on a framework that keeps a live connection open to the server, and Lighthouse's Slow 4G model treats that open connection as "the page is still loading" and refuses to call the paint done. The real, observed largest paint was already around 200 to 400 milliseconds. So a good chunk of what I "fixed" was Lighthouse's opinion of my page, not the time a human spent waiting. The marketing numbers come with their own asterisk: they're lab estimates, because the site doesn't have enough traffic yet for Google to report real field data, so I genuinely can't confirm a single mobile visitor felt them.
That's the trap token maxing sets. When effort is cheap, you'll happily spend a day making a benchmark happier, because the benchmark is right there giving you a green number and a person on a trail is not. Some of my week went to exactly that. The plan-load and payload fixes were real, a person feels those. Part of the LCP chase was me optimizing a score. Both happened, and only one of them helped the runner, and I'd be selling you a vibe if I blurred the two.
A benchmark is not a user
So the lesson isn't "buy AI credits and make everything faster." It's that surplus budget is only worth something if you point it at work you can actually check against a human. The credits got the boring speed work done. They also made it dangerously easy to polish numbers nobody outside a lab would notice. The discipline the cheap effort can't buy you is asking, every time, whether the thing you just sped up is felt by the person at the red light or only by the tool measuring him.
If there's one rule underneath it, it's this:
A benchmark is not a user. Optimize the one you can't screenshot.
The belief that sent me looking is the same one I keep landing on: a good product is a fast product, and sweating the half-seconds is the edge one person has against bigger teams who've stopped noticing them. Token maxing just lowered the price of noticing. It didn't change what's worth noticing, which is still, and only, the guy with ten seconds at the light, waiting on the thing I built to be ready before the green.