A performance budget is a number
On the difference between a budget and an aspiration, where the number should come from, and the fact that most of the bytes are spent by people who've never heard of it.
In January I set a performance budget for a client project. 200 KB of JavaScript, under a megabyte total on the homepage, and a target for how quickly the main content appears on a mid-range phone. It went in a document. Everyone agreed with it, sincerely, in a meeting.
I measured it two weeks ago. The homepage is 2.4 MB and there’s 640 KB of JavaScript.
The thing worth saying is that nobody did anything wrong. There was no moment where somebody knowingly blew the budget. There was a chat widget in March, added by marketing through the tag manager, which nobody told us about because why would they. There was a third font weight in April because the designer wanted a semibold. 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.
Each of those was a reasonable request, considered on its own, by people who had never seen my document.
A budget nobody defends is an aspiration
That’s the whole lesson and it took me until about August to accept it.
A budget is not a number. A budget is a number plus a person who has to say no. Take away the second half and what you’ve written is a wish, and a wish gets overtaken by a series of individually sensible decisions, which is exactly how every real budget in every real organization gets blown.
A budget is a number plus somebody who has to say no. Without the second half you haven't set a budget, you've written down a preference.
Which means the interesting part of this is not the tooling. The tooling is easy and I’ll get to it. The interesting part is that a performance budget is a governance mechanism dressed as a technical one, and I set it up as though it were technical, and it failed in the entirely predictable way.
Where the number comes from
Not from an article. There’s a lot of received wisdom about what a page should weigh and none of it knows anything about your client’s customers.
Three sources, in order of usefulness.
The competition. Measure three competitor sites. Set the budget so you’d be meaningfully faster than the fastest of them. This one persuades clients like nothing else, because it converts an abstraction into a league table, and every client I have ever had cares about the league table.
The client’s own analytics. Devices, connection types, geography. There’s often a specific fact in there that changes the conversation: a client last year found that a third of their sessions were on mid-range Android on mobile data, and their whole internal mental model of “our customers” was a marketing director on a MacBook on office wifi.
Where the site is now. A budget 60% below current isn’t a budget, it’s a rewrite with a number on it, and it’ll be ignored within a month. If the site is 2 MB, set 1.6 MB and win, then set 1.3 MB. A budget you can meet 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. Three numbers people can hold in their heads beat 11 that live in a dashboard.
The people who spend it aren’t in the room
Here’s the structural problem I completely missed.
The budget was written by developers, tracked by developers, and enforced on developers. And of the roughly 1.4 MB that got added to that homepage this year, I would estimate developers were responsible for about 90 KB of it.
Everything else came in through the tag manager, the CMS, the design, or a client decision. None of those routes passes through a build pipeline. None of those people had any way of knowing there was a limit, and if they had known, they had no way to find out what their request would cost.
So a budget that only exists in the repository is a diet imposed on the one person who wasn’t eating.
Making it a trade
The thing that’s actually worked since, and it’s a sentence rather than a tool.
When a request arrives, the answer isn’t no. The answer is “yes, and what comes off?”
Chat widget’s 180 KB. That’s the third font weight, 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 visible, which is both fairer and much easier than being the person who blocks things.
That framing does something else useful: it makes the budget real by making it finite. Everyone understands a fixed pot. Nobody understands “we should be mindful of performance,” which is what I’d effectively written in January.
For it to work you need the price to hand, which is why I now keep a one-page list of what the current third parties cost, updated whenever I measure. It takes ten minutes a month and it is the single most useful performance artifact I own.
Enforcing it without being hated
The pipeline part, briefly, since it’s the easy bit. Assert the sizes in CI and fail the build when they’re exceeded.
[
{ "path": "dist/js/*.js", "maxSize": "200 kB", "compression": "gzip" },
{ "path": "dist/css/*.css", "maxSize": "40 kB", "compression": "gzip" }
]
Two things I got wrong first time.
Failing hard on 2 KB over is a great way to get your check disabled. Warn at the budget, fail at a ceiling above it, and let the warning be a nudge rather than a blockage.
And an override has to exist, with a name attached. Somebody will one day genuinely need to ship over budget for a launch, and if the only options are “fail forever” or “delete the check,” they’ll delete the check. If the option is “add a line to the file with your name and a date and why,” the budget survives and so does the information.
A direction over a target
I reset the project in September. New number, derived from where it actually is now, plus a monthly measurement that goes in the same report the client already reads, next to the traffic figures. That last part has done more than anything technical, because it means the number is in front of the person who authorizes the requests, every month, whether or not I’m in the room.
The site is at 1.9 MB. Which is not good. It’s 500 KB better than August, and it went down for the first time in two years, and I’ll take a direction over a target.