What a performance budget is for
The number was never the hard part. The hard part is that the people spending it can't see what anything costs.
A designer asked me in March whether we could add a semibold for the card headings. I said yes in a chat message, inside a minute, without opening anything.
In January I had written a performance budget for that same project. 200 KB of JavaScript, under 1 MB on the homepage, and a target for how fast the main content turns up on a throttled mid-range phone. It went in a document. Everybody in the meeting agreed with it and meant it.
The semibold was 2 files and about 31 KB. I’d have known that if I’d spent 30 seconds looking, and there was nowhere to look. The number lived in a document in a folder. What a request cost lived nowhere at all.
I measured the homepage two weeks ago. It’s 2.4 MB, with 640 KB of JavaScript in it.
Nobody broke a rule to get there, and it took me until about August to accept that. There was a chat widget in March that came in through the tag manager, which nobody mentioned to us because why would they. There was my semibold in April. There was an A/B testing tool in June, a heatmap in July, and a video embed on the homepage in September that pulls in a player.
Every one of those was a reasonable request considered on its own, and four of the five came from people who had never seen my document. The fifth came from the person who wrote it.
Everybody agreed with it
A budget is not a number. A budget is a number plus somebody who has to say no, and if you take the second half away what’s left is a wish with a number on it. A wish loses to a run of individually sensible decisions, which is how every budget in every organization that has ever had one gets blown.
Which means the interesting part of this isn’t the tooling. The tooling is easy and I’ll get to it. A performance budget is a rule about who decides, dressed up as a fact about bytes. I set it up as the fact about bytes.
I wrote almost this exact post in 2016, about a hero image that grew 13 times over 4 months after I handed the site off, and the lesson I apparently took from it was to write the number down somewhere first.
A diet for the one person who wasn’t eating
The budget was written by developers, tracked by developers, and enforced on developers.
Of the roughly 1.4 MB that went onto that homepage over the year, I’d put developers at something like 90 KB of it. Everything else arrived through the tag manager, the CMS, the design, or a decision on the client’s side, and not one of those routes passes through a build. None of those people had any way to know a limit existed. If they had known, they had no way to find out what the thing they were asking for would cost against it.
So a budget that only exists in the repository is a diet imposed on the one person who wasn’t eating.
Yes, and what comes off?
When a request arrives now, the answer isn’t no. The answer is “yes, and what comes off?”
The chat widget is 180 KB. That’s the semibold, plus the carousel, plus the social sharing script. Which of those would you like to lose? And then it’s the client’s decision, made with the price showing, which is fairer than me quietly deciding and considerably easier than being the person who blocks things.
It does something else that I didn’t expect, which is that it makes the budget finite. Everybody understands a fixed pot. Nobody understands “we should be mindful of performance,” which is more or less what I’d written down in January and then asked people to feel.
For any of that to work you need the price at hand. So I keep a one-page list of what everything currently on the page costs, updated whenever I measure. It takes 10 minutes a month and it’s the most useful performance thing I own. The March question has a good answer now and it takes the same minute it took to get the wrong one.
Where the number comes from
Not from an article. There’s a lot of received wisdom about what a page ought to weigh and none of it knows anything about your client’s customers.
Three sources, in the order I reach for them.
The competition. Measure three competitor sites, then set the budget so you’d be meaningfully faster than the fastest of them. This one persuades clients like nothing else, because it turns an abstraction into a leaderboard, and every client I have ever had cares about the leaderboard.
The client’s own analytics. Devices, connection types, geography. There’s usually a specific fact in there that changes the conversation, and on a project last year it was that a third of the sessions were mid-range Android on mobile data, against an internal picture of the customer as a marketing director on a MacBook on office wifi.
Where the site is now. A budget 60% under the current weight isn’t a budget, it’s a rewrite with a number stuck on the front, and it gets ignored inside a month. If the site is 2 MB, set 1.6 MB and win, then set 1.3 MB. A budget people can hit is a budget people believe in.
And keep the list of metrics short. Total weight, JavaScript weight, and one time-based measure on a throttled mid-range phone. 3 numbers people can carry around in their heads beat 11 that live in a dashboard nobody opens.
Warn at the number, fail at the ceiling
The pipeline part, briefly, since nothing about it is hard. Assert the sizes in CI and fail the build when they’re exceeded. bundlesize is what I’ve been using, and there are several of these that all work the same way.
[
{ "path": "dist/js/*.js", "maxSize": "200 kB", "compression": "gzip" },
{ "path": "dist/css/*.css", "maxSize": "40 kB", "compression": "gzip" }
]
Two things I got wrong the first time around.
Failing hard at 2 KB over is how you get your check deleted. Warn at the budget, fail at a ceiling above it, and let the warning be a nudge instead of a wall across the road.
And an override has to exist, with a name attached to it. Somebody will one day genuinely need to go over for a launch, and if the only options are “fail forever” or “delete the check,” they will delete the check and nobody will ever put it back. If the option is “add a line to this file with your name, the date, and why,” the budget survives and so does the record of who spent what.
A direction over a target
I reset the whole thing in September. New number, derived from where the site actually is instead of where I’d like it to be, plus a measurement once a month that goes into the same report the client already reads, next to the traffic figures. That last part has done more than anything technical I did all year, because it puts the number in front of the person who authorizes the requests, every month, whether or not I’m in the room for it.
The homepage is 1.9 MB, which is not good. It’s 500 KB better than August, it went down for the first time in 2 years, and I’ll take a direction over a target.
The semibold is still on the site, incidentally. It went through the trade in September like everything else, the client looked at what it cost sitting next to a carousel nobody clicks. They kept the type. I think that was the right call. And either way it’s the first time all year that anybody spent that 31 KB on purpose, me included.