Third-party scripts
We argue about the 28 KB framework and never about the 470 KB nobody in the room has measured.
There’s a version of this conversation happening in an office right now. Somebody is arguing that the framework is too heavy, somebody else is defending it, there are numbers involved, and everyone is being reasonable and technical and enjoying themselves.
I was in it two weeks ago. 28 KB, gzipped, versus a smaller alternative, on a project where I’d measured the third-party payload the previous day at 470 KB.
So the entire disputed amount was under 6% of the thing nobody was talking about. And I don’t think that’s because anyone’s stupid. It’s that the framework is ours, so it feels like our decision and our responsibility and therefore the appropriate place to apply rigor. The tag that marketing added in March is weather.
Bytes are the least of it
The first thing to say is that even measuring these by file size understates them, in four ways.
They load more things. A tag container is maybe 30 KB. Then it fetches whatever it’s been configured to fetch, at runtime, which your build tooling has never seen and your bundle analyzer cannot know about. The 30 KB is a menu, not a meal.
JavaScript costs more than its weight. 100 KB of image is decode work on a separate track. 100 KB of JavaScript has to be downloaded, parsed, compiled and executed on the main thread, and on a mid-range Android phone that arithmetic is brutal in a way it simply isn’t on the laptop you’re testing on.
Each new origin costs a connection. DNS, TCP, TLS, before a single byte of the actual script. On a high-latency mobile connection that’s a few hundred milliseconds per domain, and a site with 11 third parties is talking to 11 domains.
And a lot of it blocks. Anything injected synchronously into the head stops the parser. Which is most analytics snippets, historically, and all of the category I’m about to be rude about.
The 30 KB tag container isn't the cost. It's the menu, and the meal arrives later, at runtime, where nothing you own is looking.
How to actually measure one
The technique that ends arguments, and it’s free and takes four minutes per script.
Devtools has request blocking. Right-click a request, block the domain, reload, and read the difference. That’s the counterfactual, measured, on the actual site, rather than an estimate from a vendor’s marketing page.
1. Network tab, throttle to Fast 3G, CPU 4x slowdown
2. Reload, record: total transfer, main-thread blocking time, first paint
3. Right-click the third-party request → Block request domain
4. Reload, record the same three numbers
5. Unblock, repeat for the next one
Two notes from doing this a lot. Throttle both network and CPU, because the CPU part is where third-party JavaScript does its real damage and an unthrottled desktop hides it entirely. And run each measurement three times, because these numbers are noisy and the first run will convince you of something wrong.
The output is a table with a row per vendor and a cost in seconds, and that table is worth more in a meeting than every principled argument I’ve ever made about page weight.
The worst one is the one meant to help
The results are usually dull, apart from one category, and this is the finding I’d most like people to have.
Client-side A/B testing and personalization tools are, by design, catastrophic.
The design problem they have is flicker: if the tool loads asynchronously, the visitor sees the original page and then watches it change, which looks broken. So the standard solution shipped by essentially all of them is an anti-flicker snippet, placed synchronously at the top of the head, which hides the entire page body until the tool has loaded and applied its variants.
With a timeout. The default timeout on the ones I’ve looked at is measured in whole seconds.
So the tool bought to increase conversion renders every visitor a blank white screen while a third-party script loads over their connection, and if that request is slow, everyone waits, and if it fails, everyone waits for the full timeout. On the site I measured last month it was the single largest contributor to time-to-first-paint, by a distance, on every page including the ones with no active tests on them.
I don’t think this is a scandal exactly. It’s an honest engineering trade the vendor made, and it’s documented, and it’s the least-bad way to do that job in the browser. But it means the tool is spending a fixed cost on 100% of visitors to possibly gain a few percent on a fraction of them, and I have never once seen that arithmetic done by the person who bought it.
The reliability cost, which never gets counted
Every third-party script is a dependency on somebody else’s uptime, inserted into your critical path, with no contract behind it.
A synchronous script from an origin that hangs will hang your page. Not fail quickly, hang, until the browser’s timeout, which is a long way past the point where your visitor has left. I’ve seen a site made unusable for about 20 minutes by a font service having a bad afternoon, and there was nothing on our side to fix, and the client’s question was reasonable and I didn’t have a good answer.
async and defer fix a lot of this and cost nothing, and should be the default for everything that isn’t genuinely required before render, which is nearly everything. The category that can’t be fixed that way is the one above, because blocking is its function.
What to do with the numbers
The same three things as last month’s budget post, because it’s the same problem.
An inventory. Every third party, what it’s for, who asked for it, what it costs in seconds. One page.
A review, on some cadence, where somebody looks at that page and asks whether each one is still earning it. Half of them won’t be. The heatmap tool that hasn’t been opened since 2017 is on almost every site I audit.
And an owner for each row, by name. Not a team. A person who can be asked “do you still want this, given it costs eight tenths of a second.”
The conversation that works is never about kilobytes. It’s blocking the script, recording the page loading with and without on a throttled phone, and playing the two side by side. Nobody argues with that. I’ve had a client cancel a subscription in the meeting.
What I kept
The analytics, for what it’s worth. It’s small, it’s async, it’s the only one anybody actually uses, and I’d rather spend the seconds there than pretend I’ve achieved purity.
And we kept the framework, which cost 28 KB, which turns out to have been the eighth largest thing on the page.