Your third-party JavaScript transfer size is creeping upward release after release, and no single change looks big enough to argue about — but the cumulative weight is now the largest slice of your payload.
Triage: Measure Where the Bytes Are Coming From
Before capping anything, quantify the current third-party transfer size and attribute it to specific vendors. A budget set without measurement is either toothless or a constant false alarm.
- Run Lighthouse and open the Reduce the impact of third-party code audit (
third-party-summary). It lists each third-party entity with its transfer size and main-thread blocking time, sorted by impact. - Cross-check with the Chrome DevTools Coverage tab (open the Command Menu, run “Show Coverage”, reload). It shows how many of each script’s bytes are actually executed — a vendor shipping 180 KB of which 15% runs is a prime budget target.
- In the Network panel, filter to
3rd-party(via the request-blocking/third-party filter) and sort by the Transfer column. Confirm you are reading the transfer (compressed, over-the-wire) size, not the larger uncompressed resource size. - Record the total third-party transfer size and the top three contributors. These numbers set your initial ceiling.
The Coverage tab is the most persuasive artifact in this triage because it separates shipped bytes from useful bytes. A session-replay recorder that transfers 160 KB but executes only a fraction on a typical page is not merely heavy — it is heavy for no rendered benefit, which makes it the obvious first candidate to defer, lazy-load, or drop. Note that Coverage reports uncompressed executed bytes, whereas your budget is denominated in transfer (compressed) size, so use Coverage to rank offenders and the Network Transfer column to size the budget. Capture the numbers on a cold load with the cache disabled, since a warm cache reports near-zero transfer and will mislead you into setting a ceiling far below reality.
Root Cause: No Size Ceiling on Cumulative Third-Party Payload
Third-party transfer size creeps because each vendor is added in isolation and nothing measures the sum. A tag manager, a session-replay recorder, two ad pixels, and a customer-support widget each look reasonable alone, but together they can dwarf your first-party bundle — and unlike first-party code, their size is set by the vendor and changes without a commit in your repository when they push a new SDK version.
There is a second, subtler driver: vendor SDK version drift. Because a third-party script is fetched from the vendor’s CDN, its size can grow without any change in your repository. A tag that was 40 KB when you integrated it can silently become 70 KB after the vendor ships a feature, and no code review will ever catch that — there is no diff. Only a ceiling measured on every build catches vendor-side inflation, which is precisely the class of regression that slips past ordinary review.
The remedy is a declared ceiling on the third-party resource type in a Lighthouse budget.json, enforced in CI so any breach fails the build. Transfer-size budgets are a resourceSizes entry within the broader budget model described in Enforcing Performance Budgets with Lighthouse CI; this page focuses only on sizing and shaping that ceiling for third-party bytes. Byte weight also compounds with request scheduling — the same payload arriving on a cold connection costs more wall-clock time, which is why capping bytes pairs naturally with optimizing the network waterfall for external assets.
Resolution: A budget.json That Caps Third-Party Bytes and Count
Create budget.json at the repository root. The third-party entry caps the aggregate compressed weight of every cross-origin vendor request; pairing it with a resourceCounts cap stops teams from evading the size limit by adding many small tags:
[
{
"path": "/*",
"resourceSizes": [
{ "resourceType": "third-party", "budget": 180 },
{ "resourceType": "script", "budget": 350 }
],
"resourceCounts": [
{ "resourceType": "third-party", "budget": 10 }
]
}
]
Every budget value in resourceSizes is kilobytes of transfer (compressed) size, matching the Transfer column in DevTools. The 180 ceiling means “no more than 180 KB of compressed third-party payload across the page”; the resourceCounts cap of 10 means “no more than ten third-party requests.”
To hold one specific vendor accountable rather than the whole bucket, add a per-host budget object. Lighthouse budgets support a budgets array keyed by host pattern, so you can cap a single origin independently:
[
{
"path": "/*",
"resourceSizes": [
{ "resourceType": "third-party", "budget": 180 }
],
"resourceCounts": [
{ "resourceType": "third-party", "budget": 10 }
]
},
{
"path": "/checkout/*",
"resourceSizes": [
{ "resourceType": "third-party", "budget": 90 }
]
}
]
The second object applies a tighter 90 KB third-party ceiling to conversion-critical /checkout/* routes while the first object’s 180 KB ceiling governs everything else. Lighthouse applies the most specific matching path to each audited URL, so a checkout page is held to the stricter limit automatically.
Choose the ceiling with a little headroom above today’s measured value, but not so much that regressions hide inside the slack. If your page currently transfers 150 KB of third-party payload, a ceiling of 180 KB tolerates minor vendor drift while still catching the addition of a new tag; a ceiling of 400 KB would let the payload nearly triple before firing, defeating the point. Ratchet the number downward over time: once you have trimmed a vendor, lower the budget to lock in the win so the reclaimed bytes cannot quietly return. Treat the budget as a one-way ratchet rather than a line that only ever moves up when a vendor is added.
A budget also gives you leverage in vendor conversations. When a tag manager or ad SDK breaches the ceiling, the failing CI check is objective evidence you can take to the vendor or to a build-versus-buy decision, rather than a subjective complaint that “the site feels slow.” The ceiling turns an abstract worry about third-party weight into a concrete, enforced contract.
Because the budget is measured against transfer size, the compression you serve directly affects headroom. A vendor script that is 180 KB under gzip may be 150 KB under Brotli, so serving Brotli where possible buys real room under the ceiling — but remember Lighthouse weighs whatever compression the vendor’s CDN applies to their own script, which you do not control. That is another reason to budget the aggregate third-party bucket rather than assume each vendor is optimally compressed.
Wire the file into your Lighthouse run through settings.budgetsPath and assert on the performance-budget audit so a breach fails CI:
{
"ci": {
"collect": {
"settings": { "budgetsPath": "./budget.json" }
},
"assert": {
"assertions": {
"performance-budget": ["error", { "minScore": 1 }]
}
}
}
}
Verification: The Budget Audit Turns Red on Breach
Verification here is not a subjective “the page feels lighter” — it is a specific row in a specific table moving to the correct side of a number you declared. Run Lighthouse against the built site with the budget attached and confirm the third-party row appears in the Budgets table with a size over/under indicator:
npx lighthouse http://localhost:4173/ --budget-path=./budget.json --view
In the report’s Budgets section, the Third-party row shows the observed transfer size and the amount over budget (for example, Third-party 204 KB +24 KB). The performance-budget audit scores 1 only when every row is within its ceiling; a single over-budget row drops the score below 1, which is why the assertion ["error", { "minScore": 1 }] fails the build on any breach. To prove the CI gate fires, temporarily drop the third-party budget to 1 and run npx lhci autorun — it must exit non-zero with a performance-budget failure that names third-party bytes. Restore the real ceiling, rerun, and confirm the audit passes with the row now within budget. The single concrete signal that the fix works: the observed third-party transfer size in the Budgets table sits at or below your declared KB ceiling, and lhci autorun exits 0.
Common Pitfalls
- Budgeting uncompressed size by mistake.
resourceSizesbudgets are compared against transfer (compressed) size. If you sized the ceiling from the uncompressed bytes shown in the Sources panel, the budget will be far too loose. Read the value from the Network panel’s Transfer column instead. - Capping size but not count. A
third-partysize ceiling alone lets a team add ten tiny pixels that each dodge the byte limit while multiplying connections and main-thread work. Always pairresourceSizeswith aresourceCountscap onthird-party. - Assuming subdomains are always third-party. Lighthouse classifies by registrable domain via its entity map, so
cdn.yoursite.comusually counts as first-party and will not appear in thethird-partybucket. If a genuinely external CDN is misclassified, target it with an explicit host-pattern budget rather than relying on the aggregatethird-partytype. - Measuring a lazily loaded vendor that never fires in the synthetic run. A tag injected only after a consent grant, an idle callback, or a scroll interaction may not download during a headless Lighthouse run, so its bytes never count against the budget even though real users pay for them. If a vendor loads conditionally, script the interaction in your collect step or budget it from field data instead, so the ceiling reflects the payload users actually receive.