Think about the last time a website annoyed you. I'd bet it wasn't a blank white screen. It was a page that looked completely finished - hero image, headline, big obvious button - and then you tapped the button and nothing happened. So you tapped again. Maybe a third time. Then everything fired at once and you'd opened a menu you didn't want.
Nobody calls that slow loading. The page had already loaded. But it's the single most 2013 experience the modern web offers, and it's almost certainly what people mean when they say your site feels dated.
That's worth sitting with, because it changes what you should go and fix.
First, the part that is literally true
Your site is almost certainly heavier than it needs to be, and the numbers are bleak.
In July 2015 the median mobile homepage weighed 845 KB. By July 2025 it had reached 2,362 KB - roughly triple, and still climbing, with growth picking up around late 2024.
Break that median page down: about 1,059 KB of images, 697 KB of JavaScript, 139 KB of fonts, 82 KB of CSS, and 22 KB of actual HTML. Read the ratio again. The words and structure - the thing people came for - are under one percent of what you ship them.
Connections got faster over that decade. Phones got faster. We spent every bit of the gain on weight, which is why the experience feels like it stood still. It did.
But the bottleneck has moved
Here's what most speed advice hasn't caught up with.
In 2013, slow meant the page takes forever to appear. That's largely solved - CDNs, better hosting and modern image formats did most of the work, and even a mediocre site now paints something within a couple of seconds.
What replaced it is worse, because it's invisible in a screenshot. The page appears. It looks ready. But the browser's main thread - the single queue where JavaScript runs, clicks get handled and the screen repaints - is still grinding. During that window your page is a photograph. It looks like software and doesn't respond.
The stretch between when a page looks finished and when it actually starts listening. Invisible in a screenshot, and the whole reason a site can pass every speed test and still feel broken.
Google formalised this in March 2024, when Interaction to Next Paint replaced First Input Delay as one of the three Core Web Vitals. The distinction matters more than a metric swap sounds. FID only measured the delay before the browser started handling your first interaction. INP measures every interaction across the whole visit, times the full round trip - input delay, event handling, and the repaint - and reports your worst one.
Under 200 milliseconds is good. Over 500 is poor. And it's the vital that sites trip over most.
The shift exposed something that had been hiding. Under FID, all your tracking code firing on every click after the first was invisible to the metric. Under INP, every one of those interactions is on the record.
The three suspects, ranked by how much they actually matter
Third-party scripts (the real villain)
Third-party code is the leading cause of poor INP across the web, and it's the one nearly nobody audits.
The mechanics are simple and brutal. A visitor clicks Add to cart. Your own handler runs - but so does the Meta pixel's event, the analytics ecommerce event, the email platform's tracker, and the heatmap tool recording the click. All of them queue on the same main thread. The visible response waits for the slowest one.
Most sites don't have a script problem, they have a ten scripts problem: tag manager, analytics, pixel, heatmap, chat widget, consent banner, review widget, A/B tester, and whatever a contractor added in 2022 that nobody can identify. Every additional third-party domain also costs a DNS lookup, handshake and TLS negotiation before one useful byte arrives - roughly 100 to 400 ms of overhead per domain, before any code even runs.
And here's the part that should change your thinking: when your own code is slow, you fix it. When a vendor's script is slow, you wait. A chat widget company has no reason to care about your INP score. That asymmetry is the strongest argument for treating every third-party tag as a cost centre that has to justify itself.
A tag that "might be useful for remarketing later" is not free. It is a permanent tax on every interaction every visitor makes.
The cookie banner deserves its own paragraph
This one is almost poetic. For most visitors the consent banner is the first thing they interact with on your site - and a badly built one is often the slowest interaction of the whole session.
The pattern: the visitor clicks Accept, the banner dumps every previously-blocked script onto the main thread at once, the browser can't repaint, and the click appears to do nothing. First impression, delivered.
Benchmarks of consent tools show a wide spread - some handle an Accept click in a few milliseconds, others block the main thread for several hundred. One popular platform has been measured injecting tens of thousands of DOM elements, against Google's guidance to stay under roughly 1,500. If your banner feels sticky when clicked, that isn't your imagination, and it isn't unavoidable.
Page builders
Builders get blamed loudly and slightly unfairly. The problem isn't usually the builder - it's that builders make adding things frictionless and give you no signal about what each thing costs.
The measurable symptom is unused code. At the median, roughly 44% of the JavaScript delivered to a mobile page goes unused during load: shipped, parsed and executed for nothing. The median desktop page now pulls more JavaScript files than images. A slider library for a slider you deleted. An icon set for the four icons you kept. Animation code for effects nobody scrolls far enough to see.
Images
Images are still the single heaviest thing on the median page - about 1,059 KB - so this remains worth doing. It's also the most solved problem on the list, which is why every article leads with it.
The fixes are dull and effective: serve WebP or AVIF instead of JPEG and PNG, size images to their actual display dimensions rather than uploading a 4,000 px photo into a 600 px slot, put width and height on every image so the layout doesn't jump, lazy-load anything below the fold, and don't lazy-load your hero - that one wants fetchpriority="high".
Do all of that and your page gets lighter and your LCP improves. Your dead zone doesn't change at all. Which brings us to the trap.
The trap: the number you're checking can't see the problem
Almost everyone checks speed the same way: paste the URL into PageSpeed Insights, read the big score, feel good or bad about it.
That score is a lab result - one simulated page load, synthetic device, modelled connection, nobody actually using the page. Nobody clicks anything in a lab test.
So INP isn't in your score. It can't be. INP only exists where real people tap real buttons, which means it comes from field data - the Chrome User Experience Report, collected from actual Chrome users. Lighthouse substitutes Total Blocking Time as a proxy, and TBT is genuinely useful, but it's a correlate, not the thing itself.
Hence the position a lot of businesses are in: a respectable score, a site that looks fast in every test, and customers whose taps land in a void. The score never described their experience, and no amount of image compression will move it.
- Search Console → Core Web Vitals. Field data from your real visitors, split by mobile and desktop. This is the source of truth.
- PageSpeed Insights, but scroll past the score to "Discover what your real users are experiencing" at the top. That section is field data. The score below it isn't.
- Lighthouse → "Reduce the impact of third-party code." Lists every third party with its main-thread blocking time. Usually an uncomfortable read.
- Chrome DevTools → Performance, recorded while you click things rather than while the page loads.
What to do, in order of payoff
Inventory your third-party scripts and delete ruthlessly. Not defer - delete. Most sites are carrying tags for tools nobody has logged into in a year. This is free, requires no developer, and is usually the biggest single win available.
Put a facade on anything heavy and interactive. Video embeds, maps, chat widgets: load a static thumbnail or a button, and only fetch the real thing when someone actually clicks. A YouTube embed has been measured blocking the main thread for seconds; a thumbnail costs nothing. Chat widgets in particular do not need to load for the 95% of visitors who'll never open them.
Fix the consent banner - async, loaded directly in the HTML rather than nested inside a tag manager, and it should yield control back to the browser the instant Accept is clicked.
Then do the images. WebP or AVIF, correct dimensions, explicit width and height, lazy-load below the fold, priority hint on the hero.
Audit what your builder ships. Turn off modules you're not using. Check unused JavaScript in the Coverage panel.
Measure in the field afterwards, not in the lab. Field data lags by weeks because it's a rolling window of real sessions, so don't panic when the number doesn't move on day one.
Two honest caveats
But if visitors are tapping your buttons twice because the first tap didn't register - that's costing you enquiries right now, it won't show up in the score you've been checking, and it's usually a script you didn't write and don't need.
Want to know what your real visitors are experiencing?
We'll pull your field data, tell you which scripts are costing you, and what we'd remove first - free, no obligation.
W