Every performance article hands you the same verbs: compress, minify, defer, remove. What none of them hands you is a number. So you compress an image, the site feels the same as before, and you have no way of knowing whether you are finished, close, or not even started.
A page weight budget is that number. Pick the connection you refuse to be slow on and the load time you are willing to defend, and arithmetic turns those two choices into a byte allowance per resource class: this many KB for images, this many for JavaScript, this many for fonts. On a regular 3G profile with a 3 second target, the whole page gets 351 KB. Suddenly “should we add this slider plugin” stops being a debate. The plugin ships 190 KB of script. The JavaScript class has 77. No.
The point of a web performance budget is not that the number is correct. No single number could be. The point is that the number exists, everyone involved can see it, and every new hero image, font weight and tracking snippet has to fit inside it or knock something else out.
A budget is a decision tool, not an optimisation
Optimisation happens after the damage. The page is heavy, so you shave bytes off it. A budget works before the damage: it is the thing you check while the designer is still choosing between one photo and three, while the plugin is still in the search results, while the font family still has six weights ticked. Most page weight is added in decisions that took thirty seconds and were never revisited. A budget makes those thirty seconds accountable.
It also tells you when to stop. If your images come in at 140 KB against a 175 KB class budget, further squeezing is a hobby, not work. The effort belongs wherever a class is over, and on most WordPress sites that means the scripts. The calculator below does the arithmetic; the sections after it explain where its numbers come from and how to fill in yours. It runs entirely in your browser tab, and nothing you type is uploaded anywhere.
Page weight budget calculator
Pick a target load time and a connection, get a byte budget for each resource class, then put your own numbers next to the budgets and see where the page rips through them. Everything is worked out in this browser tab, nothing is uploaded anywhere.
The time a visitor waits for the page, on the connection you pick on the right.
The 0.75 factor is there because real connections lose about a quarter to TLS, headers and slow start, and the two round trips taken off the top cover connection setup and TLS. This is a planning number, not physics, and that is all a budget needs to be.
The shares add up to 100%, which is not 100: the budgets below stay proportional by dividing each share by that sum instead.
A kilobyte of JavaScript costs more than a kilobyte of anything else in this list: after it arrives it still has to be parsed, compiled and executed on the main thread, which is exactly where a page feels slow. Whoever budgets only one number for the whole page is budgeting JavaScript too generously.
The arithmetic, stated honestly
The budget is one line:
budget bytes = (target time - 2 x RTT) x bandwidth x 0.75
Each term earns its place. Before the first useful byte arrives, the connection has to be built: one round trip for the TCP handshake, one more for TLS 1.3. That is why two full round trip times come off your target before any bandwidth is counted. On the slow 3G profile with its 400 ms RTT, 0.8 seconds of a 3 second target are gone before a single byte of HTML moves. HTTP/3 saves one of those trips and TLS 1.2 adds one, so two is an honest middle, not a law.
The 0.75 is a utilisation factor, and it is the honest part. A connection never delivers its nominal bandwidth for a whole transfer: TCP starts slow and ramps up, requests queue behind each other, other tabs and apps share the link. 0.75 is planning, not physics. A real waterfall wanders around these figures; the budget only needs to be conservative enough that meeting it on paper means being fast in practice.
Worked through for the default profile: 3 seconds minus two 300 ms round trips leaves 2.4 seconds. At 1.6 Mbit/s that moves 480,000 bytes, and times 0.75 that is 360,000 bytes, which is 351 KB. The calculator counts a KB as 1024 bytes and floors every figure, so the budget never flatters you. Two edge cases get the same blunt treatment: when two round trips alone exceed the target, the budget clamps to 0 KB and says in plain text that the handshake ate the whole target, and once a figure passes 1024 KB the display flips to MB.
Which profile should you plan against? Not your own connection. Look at where your visitors actually are: a shop with a mobile audience plans against regular 3G because that is what a train journey or a full stadium does to a 5G phone, while an internal dashboard for office desktops can plan against cable and spend the headroom. The presets exist so the choice is explicit instead of accidental, and the custom Mbit/s and RTT fields are there when you have real field data, for example from the network information your analytics records.
Where WordPress weight actually sits
The default split hands 5 percent to HTML, 8 to CSS, 22 to JavaScript, 50 to images, 10 to fonts and 5 to everything else. That mirrors where the bytes sit on a typical WordPress page, and the HTTP Archive has measured the same ordering for years: images first, JavaScript second, everything else far behind.
Images dominate because they are the easiest thing to add and the hardest thing to notice adding. A hero photograph exported straight from a phone camera is ten times the budget of the entire page on 3G. The fixes are mechanical rather than clever: size the file to the pixels the layout actually displays, pick a quality setting on the flat part of the quality curve instead of a superstition like “always 80”, and serve WebP so the same pixels cost fewer bytes.
JavaScript is second, and on WordPress it arrives in slices nobody chose: the page builder runtime, a slider, jQuery plus jquery-migrate, the analytics tag, the consent banner that manages the analytics tag. Each plugin enqueues its own files on every page it might conceivably be needed on. Fonts are the quiet third: every weight and style of a family is a separate file, so a family loaded in six weights can outweigh the hero image. Subsetting and woff2 compression decide whether the fonts class fits in its 10 percent.
Why a JavaScript kilobyte costs more
The budget treats all bytes equally because the wire does. The device does not. When 200 KB of JPEG arrives, it is decoded off the main thread and painted; the cost ends there. When 200 KB of JavaScript arrives, the transfer was the cheap part: it decompresses to roughly three times as much source text, which is then parsed, compiled and executed on the main thread, the same thread that responds to taps, clicks and typing. On a mid-range phone that pipeline routinely takes longer than the download did.
That asymmetry is why the split caps JavaScript at 22 percent even though it is rarely the biggest class by bytes. An image class over budget delays the moment the page looks finished. A script class over budget delays the moment the page works. When two classes are over and you can only fix one this week, fix the script one; it is also the class where deleting is easier than compressing, because half of it usually belongs to a plugin nobody remembers activating.
Getting your real numbers out of DevTools
The Yours fields want the transferred size, not the decompressed one, and DevTools shows both. Open the page in a private window, press F12, switch to the Network tab, tick Disable cache and reload. The status bar at the bottom reports two totals side by side: “transferred”, the bytes that crossed the wire, and “resources”, what they unpacked into. The budget is a wire budget, so read the first number.
The filter chips above the request list map straight onto the calculator’s classes:
Docis the HTML class,CSSandFontare exactly what they sayJSis the JavaScript class, including every third party tagImgis the images class, favicons included- whatever remains (Fetch/XHR, Media, Manifest) goes into Other
Click each chip, read the transferred total at the bottom, type it into the matching field. A class you leave empty simply stays out of the verdict, so you can budget only images and scripts on a first pass and ignore the rest.
The gap between the two totals is compression, and it deserves one glance. Text compresses dramatically; images barely compress at all, because JPEG, WebP and AVIF already are compression. The front page of this site is 316 KB of HTML markup but travels as 46 KB, an 85 percent reduction from gzip alone. You can check any page of your own from a terminal, no browser involved:
# bytes on the wire (what the budget counts)
curl -s -H 'Accept-Encoding: gzip, br' -o /dev/null -w '%{size_download}\n' https://example.com/
# bytes after decompression (what DevTools calls "resources")
curl -s -o /dev/null -w '%{size_download}\n' https://example.com/
If those two numbers come back identical for an HTML page, that is a finding in itself: your server is not compressing text, and the cheapest kilobytes you will ever save are one configuration line away.
One budget per template, not per site
“My site weighs 900 KB” is not a real sentence. Templates weigh something; sites do not. The front page with its hero and its three teasers is a different document from a single post, which is different again from an archive page, and a WooCommerce product page carries the shop’s whole CSS and JavaScript baseline before a single product image loads. Measure the two or three templates that actually receive your traffic, and keep a budget for each.
The shares are editable for the same reason. A photography portfolio can justify pushing images to 65 percent and squeezing scripts; a documentation site can do the reverse. The six fields recalculate live, and if the sum drifts off 100 the total turns red while the budgets keep computing proportionally, so an honest 105 percent split does not silently break the page, it just tells you that you are pretending to have more budget than you do.
Keeping the budget alive is mostly a calendar problem. Re-measure when something changes: a new plugin, a theme update, a redesigned header. The copy button exists precisely for this; the report it produces lists budget, actual and difference per class in plain text, which belongs in the project notes or the ticket where next month’s numbers can be diffed against it. And when a class has drifted far over and nothing obvious explains it, stop optimising at random and work through a diagnostic order until you know which layer is actually responsible.
The number is arbitrary, the discipline is not
Someone will object that 351 KB is arbitrary, and they will be right. Regular 3G is a stand-in, 0.75 is a judgment call, and your visitors are not a preset. None of that matters as much as it sounds like it should. A speed limit of 50 is also arbitrary; its value is that everyone drives to the same number. The budget’s product is the word no, delivered early, cheaply and without a meeting, to the fourth font weight and the second slider plugin.
It changes the shape of performance work, too. Without a budget, speed is a rescue project: the site got slow, someone important noticed, and now a week disappears into finding out why. With one, it is bookkeeping: ten minutes with the Network tab whenever the site changes, compared against a number that has not moved. Rescue projects are dramatic and recurring. Bookkeeping is boring and permanent, and boring is what fast sites are made of.