skip to content

Third-Party Script Cost

Analytics, chat widgets and tag managers are usually the biggest performance problem nobody owns. You should be able to quantify their cost and propose containment rather than just complain about them.

on this pageshow

questions

6

A product manager wants evidence before agreeing to drop an analytics vendor from your site. How would you measure what that one third-party script actually costs the page, in numbers rather than adjectives?

level: middleimportance: must knowfreq 62%

answer

  1. attribute by origin, not by feeling
  2. requests, bytes, main-thread milliseconds
  3. cross-origin sizes need Timing-Allow-Origin
  4. block the domain and re-measure
  5. holdback on real traffic proves it

basics

~20 s

Attribute cost per origin: count the requests and bytes that vendor's hosts pull, measure the main-thread time its scripts occupy, then block the domain and re-measure the same journey. The difference is the number to report.

solid answer

~50 s

I attribute cost by origin, not by feeling. First an inventory: group the page's resources by origin so I can state how many requests and how many bytes belong to that vendor and to anything it loads in turn. Second, main-thread attribution: group a performance profile's time by script URL, and use the third-party summary a Lighthouse run produces, which lists each entity's transfer size and main-thread blocking time. Third, and most persuasive, a controlled comparison: block the vendor's domains, run the identical journey, and report the delta in LCP, interaction latency and total main-thread time. Lab numbers convince nobody on their own, so I pair them with field data, ideally a temporary holdback where a slice of real traffic loads the page without the vendor. The deliverable is a table: requests, kilobytes, main-thread milliseconds at p75, and the measured improvement when it is gone.

code

javascript · 10 lines
javascript
const byOrigin = {};
for (const entry of performance.getEntriesByType('resource')) {
  const origin = new URL(entry.name).origin;
  const stats = (byOrigin[origin] ??= { count: 0, bytes: 0, ms: 0 });
  stats.count += 1;
  // transferSize is 0 for cross-origin responses without Timing-Allow-Origin
  stats.bytes += entry.transferSize;
  stats.ms += entry.duration;
}
console.table(byOrigin);

go deeper

for a junior

Know that you attribute cost by origin and that the browser's network view can group requests by host, giving you request counts and bytes for one vendor.

for a middle

Explain the full method: origin inventory from Resource Timing, main-thread time grouped by script URL, and the blocked-domain comparison, including why cross-origin size fields can read zero.

for a senior

Demonstrate judgment about contention and about field versus lab: run a holdback on real traffic, report p75, and measure on device classes your users actually have.

for a principal

Own the reporting format and the decision it feeds: cost in the same units for every vendor, weighed against the business metric, reviewed on a cadence rather than during a crisis.

## Why attribution has to be per origin Third-party cost is invisible to the tools that measure your own code. The script was never in your build, so a bundle report cannot see it; it is fetched at runtime from hosts you do not control. The unit that does work is the origin (scheme plus host plus port), because that is what every measurement surface can group by and what you can switch off in an experiment. ## Step 1: inventory the bytes and requests Resource Timing gives you every subresource the page fetched, with its URL, duration and transfer size, so you can total them per origin from the page itself: ```js const byOrigin = {}; for (const e of performance.getEntriesByType('resource')) { const key = new URL(e.name).origin; const s = (byOrigin[key] ??= { count: 0, bytes: 0, ms: 0 }); s.count += 1; s.bytes += e.transferSize; s.ms += e.duration; } console.table(byOrigin); ``` One trap matters here: for a cross-origin response, size and detailed timing fields are zeroed unless the server sends a `Timing-Allow-Origin` header, and vendors frequently do not. So `transferSize` for exactly the origins you care about is often 0. When that happens, take the byte figures from the browser's network log (or a synthetic run) instead, and use Resource Timing for the request count and the origin list. Also record how many distinct origins the vendor drags in. One approved tag pulling from four hosts means four connection setups and four vendors you never reviewed. ## Step 2: attribute main-thread time Bytes are the easy half; the expensive half is execution. Record a performance profile of the page load plus a representative interaction, then group the flame data by script URL rather than by function, so all the time under the vendor's scripts collapses into one number. A Lighthouse run produces the same view for you as a third-party summary listing each entity with its transfer size and main-thread blocking time, which is usually the single most quotable artefact for a non-engineer. In the field, Chromium's Long Animation Frames API reports long frames together with the scripts that ran in them, including a source URL, so you can attribute real users' slow frames to a vendor rather than guessing. Support is Chromium-only, so treat it as a strong signal from part of your traffic, not a census. ## Step 3: the removal experiment Everything above tells you what the vendor consumes. Only removal tells you what the page gains, because costs interact: a script that occupies the main thread while nothing else needs it may cost far less than its raw milliseconds suggest, and a script that lands exactly during hydration may cost far more. In the lab, block the vendor's domains in the browser's network panel or your synthetic tool, then run the identical scripted journey with and without, several times, and compare medians. In the field, run a holdback: for a small percentage of sessions, do not load the vendor, and compare Core Web Vitals at the 75th percentile between the two groups. A holdback is also the only honest way to measure the other side of the ledger, which is whether the vendor's business value survives its absence. ## Reporting it so a decision can happen The output should fit in one table and one sentence. Something like: this vendor loads 11 requests from 3 origins, 210 KB transferred, 480 ms of main-thread time on a mid-tier phone, and blocking it improves p75 LCP by 0.4 s and p75 INP by 90 ms. That is a number a product manager can weigh against the vendor's contribution. Two honesty rules keep the exercise credible. Measure on hardware and networks like your users', not a developer laptop on office wifi, because third-party execution cost scales with CPU. And separate the cost of loading from the cost of running: a widget with modest load cost but a document-level listener on every interaction shows up in interaction latency, not in load metrics, and you must look there to find it.

  • Your Resource Timing entries for the vendor report transferSize as 0. What happened?
    The vendor's responses lack a `Timing-Allow-Origin` header, so the browser zeroes size and detailed timing fields for those cross-origin entries to avoid leaking information. The entries still give you the URLs and overall duration; take byte figures from the network log or a synthetic run instead.
  • Why isn't the vendor's total script execution time on its own a fair statement of its cost?
    Because cost depends on contention. Time spent while the main thread is otherwise idle costs users little; the same milliseconds landing during hydration or while a user is tapping cost a lot. That is why the removal comparison, not the raw total, is the number worth reporting.
  • How would you keep this measurement from going stale after the vendor is kept?
    Monitor it in the field continuously: track requests, bytes and long-frame attribution grouped by origin in your real-user data, and alert when a new origin appears or a known one grows. Vendor payloads change without any deploy of yours, so a one-off audit decays quickly.

saying these in an interview costs you the question

  • Quoting a Lighthouse score instead of per-vendor numbers
  • Measuring only on a fast laptop and office network
  • Assuming a bundle analyzer covers third-party scripts
  • Reporting execution time without a with/without comparison
  • Ignoring interaction cost because load metrics look fine

context

open as a page

Your team adds a vendor chat widget by pasting a single `<script src>` tag pointing at the vendor's own CDN, and the file it downloads is about 40 KB compressed. Why is the real cost of that widget to the page usually far larger than that number?

level: juniorimportance: should knowfreq 52%

basics

~20 s

The tag is only an entry script. It opens a connection to a new origin, then fetches more code and data at runtime, and everything it pulls parses and executes on your main thread, on the vendor's release schedule rather than yours.

open as a page

What is the facade (click-to-load) pattern for an expensive third-party embed such as a video player or a live chat widget, and what does adopting it trade away?

level: middleimportance: should knowfreq 46%

basics

~20 s

A facade is a cheap static stand-in you build yourself — a poster image with a play button, a plain chat button — that loads the real third-party code only when the user shows intent. Most visitors never pay the cost; those who engage wait once.

open as a page

A team proposes self-hosting a third-party vendor's JavaScript from your own domain instead of loading it from the vendor's CDN. What does that buy you in performance terms, and what do you take on?

level: seniorimportance: should knowfreq 38%

basics

~20 s

Self-hosting removes an extra origin handshake, lets you set your own cache lifetime and priority, and lets you pin a reviewed version. In exchange you own updating it, some vendors break or forbid it, and the script often still calls home anyway.

open as a page

On a site where a marketing team publishes tags through a tag-manager container, page weight and main-thread time keep growing in weeks when the frontend team ships nothing at all. Why is that cost so hard to control, and what would you put in place?

level: seniorimportance: should knowfreq 42%

basics

~20 s

A tag-manager container is a second deploy channel that bypasses code review, CI and your bundle checks, and each tag can load further vendors at runtime. Control it with an owner and expiry per tag, publishing restrictions, a script-source allowlist, and field monitoring by origin.

open as a page

As the engineer accountable for a site's speed, how would you decide which third-party vendors are worth their performance cost, and what would you do about the ones that are not?

level: principalimportance: should knowfreq 33%

basics

~20 s

Put cost and value in the same table: measure each vendor's requests, bytes and main-thread time, then run a holdback to see whether its claimed business value survives its absence. Keep what pays, contain the rest by tier, and make admission a reviewed decision.

open as a page