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:

VendorDomains
Tag managerwww.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 platformcdn.cookielaw.org, geolocation.onetrust.com
A/B testingcdn.optimizely.com, logx.optimizely.com
Chatwidget.intercom.io, js.intercomcdn.com, api-iam.intercom.io
Session replaystatic.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.

Method for measuring a vendor's cost by blocking its domains Map the vendor to all its domains from a HAR file. Run Lighthouse five times with nothing blocked and five times with the vendor's domains blocked. Take the medians of LCP, TBT, bytes and requests for each variant. Subtract to get the vendor's cost. Treat differences smaller than baseline run-to-run variance as noise. One vendor, measured by absence Map domains from a HAR file Baseline runs 5 runs, nothing blocked Blocked runs 5 runs per vendor Medians LCP, TBT, bytes, requests Cost baseline minus blocked
The subtraction captures indirect effects that per-origin attribution misses, such as delayed first-party resources.

Step 3: Read the Results

Home page, Lighthouse mobile, medians of five runs per variant:

Vendor blockedLCP savedTBT savedBytes savedRequests saved
A/B testing620 ms90 ms118 KB4
Tag manager (with children)180 ms290 ms204 KB12
Consent platform240 ms120 ms96 KB5
Chat30 ms (noise)250 ms312 KB9
Session replay20 ms (noise)110 ms71 KB3

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.

LCP and Total Blocking Time added by each third-party vendor Paired bars of the cost of each vendor measured by blocking. A/B testing added 620 milliseconds of LCP and 90 of TBT. The consent platform added 240 of LCP and 120 of TBT. The tag manager with its tags added 180 of LCP and 290 of TBT. Chat added 30 of LCP, within noise, and 250 of TBT. Session replay added 20 of LCP and 110 of TBT. LCP and TBT cost per vendor LCP added TBT added A/B testing 620 ms 90 ms Consent platform 240 ms 120 ms Tag manager + tags 180 ms 290 ms Chat widget 30 ms 250 ms Session replay 20 ms 110 ms
Lighthouse mobile, medians of five runs; LCP differences under 60 ms are within run-to-run noise.

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.
Decision outcomes for third-party vendors after measuring their cost The root asks whether the vendor's measured cost is worth its value. If not needed, remove it, as with A/B testing. If needed but replaceable, replace it with a lighter first-party alternative, as with the consent platform. If needed and used by few readers, put it behind a facade, as with chat. If needed by all, defer and sandbox it, as with the tag manager and session replay. What to do with each vendor Is the measured cost worth it? Not needed Remove A/B testing snippet Needed but replaceable Replace lighter self-hosted consent Used by few readers Facade chat widget Needed everywhere Defer and sandbox tag manager, replay
Each branch was taken by at least one vendor on this site.

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

MeasureBeforeAfter
Third-party requests on home page load316
Third-party bytes on home page load801 KB74 KB
LCP p75 mobile, field2.9 s1.8 s
INP p75, field260 ms120 ms
TBT, lab910 ms140 ms
Vendor decisions backed by measurement05 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.

Filed underHow-tosLighthouse