Why your vibe coded site feels heavy on mobile, and how to find the real weight
The same page scored 68 on one run and 58 on the next, and I hadn't touched a line of code in between. I was crouched over a mid-range phone plugged into my laptop, reloading a site I'd vibe coded the week before, trying to work out whether it was actually slow or just in a mood. Two runs, ten points apart, same bytes. That gap is where I almost went wrong, because the obvious move when a page feels heavy is to grab the part that looks heavy and start cutting.
So here's the short version I'd give any vibe coder whose site feels fine on the laptop and heavy on a phone. Don't optimize anything yet. The part that looks expensive, the animation or the shader or the big interactive effect, is almost never the bottleneck, and the score you're reading to check yourself is probably lying to you by ten points in either direction. The first move is to get a number you can trust: measure on a real phone, gate on total blocking time instead of the headline score, and take the median of several runs. Then set a page-weight budget you refuse to cross. Only then will you see the real weight, which is usually the payload your generator bloated, not the math that looks scary.
why won't my performance score sit still?
Because a single mobile performance run is a surface signal, not a measurement. The score gets assembled from a simulated device on a throttled network, and both of those are sampled fresh each pass, so two back-to-back runs on identical code can land ten points apart. Mine read 68, then 58, then something in the middle, and for about twenty minutes I chased the 58 like it meant a regression had crept in. Nothing had. That's the bimodal floor: the number bounces inside a range, and if you treat any single run as the truth you'll spend the day reacting to sampling luck instead of your site.
The fix is to stop trusting the headline number and start trusting a stable one. I gate on total blocking time now, the stretch where the main thread sits locked and can't answer a tap, because it maps to the thing a visitor actually feels and it moves for real reasons rather than for luck. And I take the median of five runs, not the best one and not the last one. Five boring runs tell you more than one dramatic one ever will. That is the whole of measuring in the real environment applied to performance: check where your visitor actually is, on the hardware they actually hold, with a statistic that doesn't flinch. Your laptop, plugged in with a fast chip and a fat pipe, is close to the worst place on earth to judge how heavy your site feels.
so what's actually making my vibe coded site heavy?
Almost always the payload, not the compute. When I finally had a number I trusted and opened the trace on the phone, the first honest measurement put the JavaScript at around 1,700 milliseconds of main-thread work. The 64-step raymarcher shader I'd been ready to gut, the liquid-metal hero I assumed was the villain because it was the flashiest thing on the page, cost almost nothing next to it. The shader wasn't the bottleneck. The scripts around it were, and I'd never have guessed that from my laptop, where 1,700 milliseconds of parsing vanishes into fast hardware.
This is the shape of most heavy vibe coded sites, and it falls straight out of how the tools build. An AI generator reaches for a whole icon set to place three icons, pulls in an entire charting package for one sparkline, and drops the hero image in at full resolution because nobody told it not to. Each choice is locally reasonable and collectively fatal on cellular. The weight isn't in the part you designed and are proud of. It's in the dependencies you never chose, shipped in full to a phone that has to read every byte before it can paint. If you want the longer catalog of where that weight hides, I went through it in why AI-built sites load slowly, but the pattern is always the same: the generator optimized for working, not for weighing little.
how do I fix it without guessing?
Set a budget first, measure against it, then cut. A page-weight budget is just a number you decide the site isn't allowed to cross, a ceiling on total transferred bytes and a ceiling on total blocking time, written down before you change anything. It turns a vague "make it faster" into a pass-or-fail gate. This is the same spec-first discipline that runs through the rest of serious vibe coding: name the property you refuse to break, then let that decision drive the work instead of finding the limit later in the wild. Once the budget exists, every change has an answer. Did the median blocking time come in under the ceiling? Did the transfer size fit? If not, you're not done, no matter how much tidier the code reads.
With the budget in place, the cuts get boring and high-leverage. Import the three icons, not the set. Load the chart only on the page that shows a chart, and only after the content paints. Right-size and lazy-load the hero image. Split the bundle so a visitor downloads what the first screen needs and nothing more. Only once the payload sits inside the budget is it even worth looking at the effect itself, and by then you usually find it was fine, or that the right move is to serve a light static version to weak devices and switch the live one on behind a real capability check. I wrote about that half in the hero animation that broke my site on mobile. This post is the upstream part, where you figure out what to cut before you touch anything.
If you're scaling personal site work into client builds, this measurement discipline is most of what separates a site that demos well from one that holds up on a stranger's phone. If you want a sparring partner on that production jump, /work-with-us. Send me the site that feels heavy and the score you don't trust, and I'll set up the budget, the on-device median, and the blocking-time gate, then find the real weight with you instead of guessing at it. Work with VibeKoded.
questions that keep coming up
Which number should I actually watch? Total blocking time, as a median of several runs on a real mid-range phone. The headline performance score is fine as a rough temperature, but it's bimodal enough that any one run can send you chasing a ghost. Blocking time maps to the frozen-tap feeling a visitor gets, and the median kills the run-to-run noise.
Do I need a real phone, or is the simulator enough? A real mid-range phone, every time. The simulator and your laptop both flatter the site, because they have headroom a three-year-old Android does not. The point is to measure where your visitors are, not where you build. When the on-device number disagrees with the desktop one, the phone is right.
Isn't a page-weight budget overkill for a small site? It's less work than it sounds and it pays for itself the first time it catches a stray library. The budget is one or two numbers in a doc. Its job is to make "is this fast enough" a question with a yes-or-no answer instead of a vibe, which is exactly what you want the moment a vibe coded site stops being a toy and starts having visitors.
The hero I was ready to tear out is still there, still rippling, for anyone whose phone can render it without complaint. It turned out it was never the problem. The problem was everything I'd shipped around it without weighing, and I only found that because I stopped trusting the score on my screen and started measuring the site my visitor actually gets.
// part of the ai websites topic
// grab the free starter kit that makes your AI stop forgetting and stop guessing: get it →
// building with AI? the field manual has the structured lessons.
// hitting this on a real build? this is what I fix →