Third-party scripts and Core Web Vitals
Third-party scripts are the single most common reason a site has bad Core Web Vitals and the developer can't figure out why. Your own code is fast. The bundle is small. CLS is zero in dev. And then real users see INP of 400ms because the chat widget you added "to help conversion" hogs the main thread for 600ms on every interaction.
The pattern, in three numbers
Real CrUX data we've seen across hundreds of sites: a single chat-widget script can add 80–250ms to INP, 200–800ms to LCP, and 0.02–0.08 to CLS — entirely on its own, with no other changes. Stack three or four such scripts and you can blow through every Core Web Vital simultaneously.
The scripts most likely to cause damage:
- Live chat widgets (Intercom, Drift, LiveChat, Tawk.to) — typically the worst
- A/B testing platforms (Optimizely, VWO, AB Tasty) — synchronously block render to prevent flicker
- Analytics + tag managers (Google Tag Manager especially, with multiple tags inside)
- Marketing automation (Hubspot, Pardot, Marketo)
- Customer-data platforms (Segment, Tealium)
- Heatmap / session replay (FullStory, Hotjar, LogRocket — second-worst after chat)
- Ad networks (any third-party ad with auction)
- Cookie banners (Cookiebot, OneTrust — render-blocking by design)
- Social embeds (Twitter, Instagram, Facebook iframes)
What each hurts most
| Script type | LCP | INP | CLS |
|---|---|---|---|
| Live chat | minor | severe | minor |
| A/B testing | severe | minor | minor |
| Cookie banners | minor | minor | severe |
| Heatmap / session replay | minor | severe | none |
| Tag manager (loaded) | moderate | moderate | minor |
| Ad networks | moderate | moderate | moderate |
| Social embeds | minor | none | moderate |
| Marketing automation | minor | minor | minor |
Severe = you can blame this single script. Moderate = it adds to the pile.
Why chat widgets specifically
A chat widget needs to:
- Authenticate the user (or create an anon session)
- Subscribe to a real-time channel
- Render a button or icon in the corner
- Load custom fonts for its UI
- Listen for clicks site-wide for unread-message popovers
Most of that runs JavaScript on every interaction. Even if the user never opens the chat, the widget polls and processes events. INP is the metric that catches this — every click on your page now has the chat widget's event listeners running in parallel.
The audit: find your offenders
In Chrome DevTools → Performance tab → Record 5 seconds of interaction. Look at the JavaScript flame chart. Anything that isn't from your origin or _next/static is third-party. The ones with wide flame bars are the expensive ones.
Or, faster: open DevTools → Network tab → reload → sort by Transferred. Anything over 50KB from a non-self origin is a candidate. Anything over 200KB is almost certainly hurting performance.
Or, easiest: run Request Map — it visualizes every third-party call as a network graph.
The three remediations
1. Defer with loading="lazy" (intersection-observer pattern)
Don't load chat widgets, social embeds, or analytics until the user scrolls to them or after onload. Many vendors publish "deferred" variants:
<!-- Bad: blocks render -->
<script src="https://widget.intercom.io/loader.js"></script>
<!-- Better: load after page is interactive -->
<script>
window.addEventListener('load', () => {
setTimeout(() => {
const s = document.createElement('script');
s.src = 'https://widget.intercom.io/loader.js';
document.head.appendChild(s);
}, 2000);
});
</script>
This typically recovers 50–150ms of INP and 100–400ms of LCP.
2. Use <Partytown> or Web Workers
Partytown is a runtime by the Builder.io team that moves third-party scripts off the main thread entirely. It works for most analytics and tag managers, partially for ad networks, and doesn't work for chat widgets (they need DOM access).
Setup is ~30 minutes. The win on heavy-script sites is dramatic — we've seen INP go from 380ms to 95ms by moving GTM + Hotjar + Hubspot to Partytown.
3. Remove what you don't need
The cheapest win. Audit your tag manager once a quarter. Most sites accumulate 5–15 tags over time; half are usually for campaigns that ended months ago. Each one consumes main-thread time on every page load.
Marketing-team pushback: "but we might need it." Counter: "we lost 12% of mobile traffic because of these tags." Have the metric ready.
What about Google Tag Manager itself?
GTM is fine when it has 1–3 tags inside. GTM with 20 tags is a disaster waiting to happen — every tag loads in a child context that adds main-thread time. If you have a heavy GTM, deferred-loading the GTM container itself often costs you analytics but saves real user experience. Make the call based on which metric matters more for your business.
What our tool can show you
Search any domain on this site. If LCP and CLS look fine but INP is high, that's a third-party script tell — your own code rarely causes high INP, but heavy scripts always do. Compare mobile and desktop INP: if mobile is 3× worse, the bottleneck is CPU (script execution), not network.
The CrUX number you see is the real-world impact. It's what Google ranks on. Fix it.
By Paulo de Vries · Published