Measuring Third-Party Cost with Request Blocking
Every vendor claims its script is lightweight. The tag manager is "just a container", the analytics snippet is "under 30 KB", the chat widget "loads asynchronously". Lighthouse's third-party summary shows bytes and main-thread time per origin, which is a start, but it misses the indirect costs: the tag manager that loads four more scripts, the consent platform that delays the LCP image by holding the main thread, the A/B testing snippet that hides the page until it runs. The only reliable way to know what a vendor costs a page is to load the page without it and compare.
This guide measures each third-party script on a static marketing site by blocking its domains in Lighthouse, WebPageTest and Playwright, compares LCP, Total Blocking Time, bytes and requests with and without it, and turns the results into a table that makes decisions easy. It is part of Third-Party Script Performance on Static Sites, and complements Auditing Third-Party Scripts with Lighthouse.
Prerequisites
- A list of third-party domains per vendor, including domains they load secondarily (a HAR file from a normal page load shows them).
- Lighthouse CLI 12, and optionally WebPageTest and Playwright.
- A representative page per template.
Step 1: Map Vendors to Domains
A HAR export of the home page listed 31 third-party requests across 14 domains. Grouping them by vendor is the first step, because blocking must cover everything a vendor loads:
| Vendor | Domains |
|---|---|
| Tag manager | www.googletagmanager.com |
| Analytics (loaded by tag manager) | www.google-analytics.com, region1.google-analytics.com |
| Ads pixel (loaded by tag manager) | connect.facebook.net, www.facebook.com |
| Consent platform | cdn.cookielaw.org, geolocation.onetrust.com |
| A/B testing | cdn.optimizely.com, logx.optimizely.com |
| Chat | widget.intercom.io, js.intercomcdn.com, api-iam.intercom.io |
| Session replay | static.hotjar.com, script.hotjar.com |
Tag managers complicate attribution: blocking the tag manager also blocks everything it loads. Measure both ways — the container alone (block only its children) and the container with its children — to see what the tags cost versus the container.
Step 2: Block in Lighthouse
Lighthouse accepts URL patterns to block. A small script runs five runs per variant — baseline, and one variant per vendor blocked — and records medians:
#!/usr/bin/env bash
# scripts/vendor-cost.sh URL
URL=$1
declare -A VENDORS=(
[baseline]=""
[ab-testing]="*optimizely.com*"
[tag-manager]="*googletagmanager.com*"
[consent]="*cookielaw.org*,*onetrust.com*"
[chat]="*intercom.io*,*intercomcdn.com*"
[replay]="*hotjar.com*"
)
for name in "${!VENDORS[@]}"; do
for i in 1 2 3 4 5; do
npx lighthouse "$URL" --quiet --only-categories=performance --output=json \
--blocked-url-patterns=${VENDORS[$name]//,/ --blocked-url-patterns=} \
--output-path="out/$name-$i.json" --chrome-flags="--headless=new"
done
done
node scripts/summarize-vendor-cost.mjs out/ # medians of LCP, TBT, bytes, requests per variant
Each vendor's cost is the baseline median minus the median with that vendor blocked. Variance across the five baseline runs (LCP ±60 ms, TBT ±40 ms) set the threshold below which a difference was treated as noise.
Step 3: Read the Results
Home page, Lighthouse mobile, medians of five runs per variant:
| Vendor blocked | LCP saved | TBT saved | Bytes saved | Requests saved |
|---|---|---|---|---|
| A/B testing | 620 ms | 90 ms | 118 KB | 4 |
| Tag manager (with children) | 180 ms | 290 ms | 204 KB | 12 |
| Consent platform | 240 ms | 120 ms | 96 KB | 5 |
| Chat | 30 ms (noise) | 250 ms | 312 KB | 9 |
| Session replay | 20 ms (noise) | 110 ms | 71 KB | 3 |
The A/B testing snippet was the surprise. It is loaded synchronously and hides the page body until experiments apply — an "anti-flicker" pattern — so it delayed LCP by 620 ms although it was the third-smallest vendor by bytes. Per-origin attribution in the Lighthouse report had listed it as 118 KB and 90 ms of main-thread time, which undersold its effect by an order of magnitude. The consent platform similarly delayed LCP by blocking the main thread during the critical rendering window.
Step 4: Confirm With Field-Like Runs
Simulated throttling can misjudge scripts that change loading order, so the two largest findings were confirmed on WebPageTest with packet-level throttling on a Moto G4 over 4G, nine runs per variant, using blockDomains in a script:
blockDomains cdn.optimizely.com logx.optimizely.com
navigate https://www.example.com/
The A/B testing snippet's LCP cost measured 710 ms there — slightly larger than in Lighthouse. For ongoing monitoring, the same blocking can run in Playwright with page.route('**/*optimizely.com/**', (r) => r.abort()) inside the synthetic checks from Uptime and Synthetic Checks for Static Sites.
Step 5: Turn Costs Into Decisions
The table went to the owners of each vendor with a question: is this cost worth it? The answers:
- A/B testing: the team ran two experiments a year. The snippet was removed; experiments now use build-time variants served by an edge function, which costs nothing on pages without experiments.
- Consent platform: legally required. Replaced with a lighter self-hosted consent banner that renders in the static HTML and loads no remote script, as in Fixing CLS from Cookie Banners.
- Tag manager: kept, moved after first interaction and into Partytown, following Loading Google Tag Manager Without Hurting INP.
- Chat: replaced with a facade, as in Facades for Chat Widgets and Maps.
- Session replay: limited to 5% of sessions and loaded after idle.
Making It a Routine
A one-off audit decays as quickly as the site changes: marketing adds a pixel for a campaign, a new tool for heatmaps appears in the tag manager, a vendor updates its snippet and doubles its size. Two routines kept the numbers current. The blocking script runs monthly on a schedule against the production home page and one page per template, and posts the vendor table with a comparison to the previous month; any vendor whose LCP or TBT cost grew by more than 50 ms is highlighted. And any request to add a new third-party script now comes with a blocking measurement on a preview deploy, before and after adding it, attached to the request. The requirement did not block anything outright; it changed conversations from "can we add this tag" to "this tag costs 140 ms of LCP on the pricing page — is it worth it, and can it load after interaction?"
It also helped to share the method, not just the results. Once marketing colleagues could run the script themselves against a preview URL, they began testing new tools before asking for them, and two proposed vendors were dropped by the people who had proposed them after seeing their cost.
Measured Impact
| Measure | Before | After |
|---|---|---|
| Third-party requests on home page load | 31 | 6 |
| Third-party bytes on home page load | 801 KB | 74 KB |
| LCP p75 mobile, field | 2.9 s | 1.8 s |
| INP p75, field | 260 ms | 120 ms |
| TBT, lab | 910 ms | 140 ms |
| Vendor decisions backed by measurement | 0 | 5 of 5 |
Pitfalls & Rollback
- Blocking only the primary domain. Vendors load from several domains; map them from a HAR first.
- Single runs. Differences smaller than run-to-run variance are noise.
- Pages that break when a vendor is blocked. Errors change the measurement; guard vendor calls.
- Trusting per-origin attribution alone. It misses indirect effects like anti-flicker hiding.
- Rollback: measurement changes nothing on the site; the vendor decisions that follow are separate, reversible changes.
Conclusion
The cost of a third-party script is the difference between the page with it and the page without it. Map each vendor's domains, block them in repeated Lighthouse runs, confirm large findings with packet-level tests, and bring the numbers to the people who own each vendor. On this site the measurements turned vague complaints about "too many scripts" into five concrete decisions that took LCP from 2.9 s to 1.8 s.
FAQ
Why measure third-party cost by blocking requests?
Because attribution inside a single trace is unreliable. Third-party scripts often trigger further requests, delay other resources and cause work in first-party code. Comparing runs with and without the vendor's domains blocked captures all of those effects in one number per metric.
Which tools support request blocking?
Lighthouse via the blockedUrlPatterns setting, Chrome DevTools request blocking in the Network panel, WebPageTest with the block option or a blockDomains script command, and Playwright with page.route. They all prevent matching requests so the page loads as if the vendor were absent.
How many runs are needed for a reliable comparison?
Enough that the difference is larger than run-to-run variance. With Lighthouse's simulated throttling, five runs per variant and the median were usually sufficient on the sites measured here; for field-like WebPageTest runs, nine runs per variant.
Does blocking a script break the page?
Sometimes. If first-party code calls a vendor API without checking that it loaded, blocking causes errors that change the measurement. Check the console with the vendor blocked, and guard vendor calls so the page degrades cleanly, which is a good practice anyway.
Related
- Parent: Third-Party Script Performance on Static Sites — tiering and loading vendors.
- Auditing Third-Party Scripts with Lighthouse — the per-origin view.
- Self-Hosting Analytics to Cut Third-Party Requests — replacing a vendor with first-party code.
- Running WebPageTest Scripts Against Preview Deploys — scripted blocking runs.
- Reserving Space for Ad and Promo Slots — when the third party is an ad server.
Filed underHow-tosLighthouse