awesome-everything RU
↑ Back to the climb

Performance

Third-party scripts: the silent budget killer

Crux Analytics, ads, chat widgets, and A/B testing tools routinely add 500 KB to 1 MB across a page — outside your control but inside the user''''s cost. Three patterns mitigate them.
Your altitude — climbing toward senior
ZeroJuniorMiddleSenior
You are at middle altitude — in the sky
◷ 14 min

A team spends a sprint cutting their app bundle from 800 KB to 350 KB. Lighthouse score improves by 12 points. A week later the data team adds Segment, Hotjar, and a new Intercom widget — collectively 450 KB. The sprint’s work is erased. No one noticed because third-party scripts are not in the app repository.

The third-party tax

Most production web apps load 5-15 third-party scripts per page. Common ones and their approximate sizes:

  • Segment analytics: ~150 KB
  • Hotjar recording: ~200 KB
  • Intercom chat widget: ~200 KB
  • Google Tag Manager (with several tags): ~500 KB+
  • Full Optimizely A/B testing: ~400 KB
  • A single poorly optimised ad tag: ~300 KB

Ten average scripts × 100 KB = 1 MB of third-party JS before your own code. None of this appears in your bundle analyzer because the files load from external domains at runtime. None of it can be tree-shaken because you do not control the source. And most of it runs on the main thread, blocking paint and interaction.

The problem compounds: business teams often add scripts through Google Tag Manager without engineering review. Each tag manager addition escapes code review entirely.

Pattern 1: Interaction gating

Load the script only when the user takes an action that requires it. The chat widget loads when the user clicks “Help”. The analytics SDK loads after the first meaningful interaction. The newsletter modal script loads when the user scrolls to 80% of the page.

<!-- Defer until user clicks -->
<button id="chat-btn">Chat with us</button>
<script>
  document.getElementById('chat-btn').addEventListener('click', () => {
    const script = document.createElement('script');
    script.src = 'https://widget.intercom.io/widget/APP_ID';
    document.body.appendChild(script);
  });
</script>

Tradeoff: the first interaction that triggers the load has a visible delay as the chunk downloads and parses. Mitigate with prefetch when the trigger element becomes visible (IntersectionObserver on the button) or on mouseenter.

Pattern 2: Web Worker isolation via PartyTown

PartyTown is an open-source library (Builder.io) that runs third-party scripts in a Web Worker via a service-worker proxy. DOM access from the Worker is bridged via synchronous postMessage. The main thread is freed from the analytics, ad, and tracking scripts entirely.

<script>
  partytown = { forward: ['dataLayer.push', 'fbq', 'gtag'] };
</script>
<script src="/~partytown/partytown.js"></script>
<script type="text/partytown" src="https://www.googletagmanager.com/gtm.js?id=GTM-XXXX"></script>

Tradeoff: scripts that rely on synchronous DOM reads are incompatible (rare for analytics). postMessage round-trips add ~5-15 ms per call. Some scripts break when proxied. Test each vendor before deploying.

Pattern 3: Server-side measurement

Move analytics events to the server. Instead of shipping a 150 KB Segment client to every user, fire Segment events from your API server on relevant actions. Users get the tracking; browsers get nothing.

Similar options: Google Analytics Measurement Protocol, Sentry server-side error capture for SSR errors, custom event pipeline via a lightweight beacon endpoint.

Tradeoff: you lose browser-only context — user agent details, real-time client errors, click heatmaps. Suitable for core funnel analytics; combine with a lean client SDK for browser-specific signals.

PatternMain-thread costTradeoff
Interaction gatingZero until triggeredFirst-use latency; mitigate with prefetch
PartyTown Web WorkerNear-zero (proxied to Worker)DOM-sync incompatible scripts; postMessage overhead
Server-side measurementZero (nothing on client)Loses browser-specific context

Governance: a separate third-party budget

Senior teams maintain a separate third-party budget alongside the app bundle budget. Concretely: a third-party-budget.json file in the repo that lists every permitted third-party domain, its approved size, and the owning team. Any new script added via GTM or <script src> that is not in the file fails CI.

Complementary enforcement: CSP (Content Security Policy) script-src header lists allowed origins. Scripts from unlisted origins are blocked by the browser. This prevents accidental or malicious third-party script injection. Quarterly audit reviews every entry, removes unused vendors, and checks vendor track records.

Why this works

Why do third-party scripts disproportionately hurt? (1) Vendors prioritise features over bytes — a chat SDK supports every use case even if the app uses 5%. (2) No tree shaking — you cannot remove unused vendor code. (3) Cumulative effect — 10 scripts average 100 KB each = 1 MB before your own code. (4) Main-thread blocking — most run synchronously on page load unless explicitly deferred.

Quiz

Marketing wants a 200 KB chat widget on every page and says it must load immediately. Senior compromise?

Quiz

A team runs five analytics scripts through GTM totalling 600 KB. They migrate funnel events to server-side Measurement Protocol. What is the expected client-side impact?

Quiz

Why is a Content Security Policy (CSP) script-src header relevant to third-party budget governance?

Recall before you leave
  1. 01
    Why do third-party scripts disproportionately inflate bundle cost compared to first-party code?
  2. 02
    Describe the interaction gating pattern and its tradeoff.
Recap

Third-party scripts regularly add 500 KB to 1 MB to every page, outside the app repo and outside code review. Three structural mitigations: interaction gating (load on trigger, zero initial cost), PartyTown Web Worker isolation (main thread freed), and server-side measurement (nothing on client). Governance via a third-party budget file and CSP enforce the allow-list at CI and browser level respectively. The next lesson covers CI enforcement: how to make the budget a hard constraint rather than a guideline.

Connected lessons
appears again in159
Continue the climb ↑CI enforcement and RUM: making budgets stick
shortcuts expand
search
K
prev piece
k
next piece
j
cycle tier
t
this menu
?
sources2
expand
  1. 01
  2. 02

Trademarks belong to their respective owners. Editorial reference only.