---
title: "Fix Third-Party Script Impact on INP"
description: "Third-party scripts compete for main thread time. Learn to defer, facade, and limit their impact on interaction responsiveness."
canonical_url: "https://unlighthouse.dev/learn-lighthouse/inp/third-party-scripts"
last_updated: "2025-01-18"
---

Third-party scripts consume an average of 30% of main thread time on commercial websites. That's not your code causing INP problems - it's analytics, ads, chat widgets, and embedded content fighting for the same thread your users need for interactions.

## What's the problem?

Every third-party script runs on the main thread. When a user clicks a button, but the browser is busy executing Google Tag Manager callbacks or ad rendering code, that click waits. The [HTTP Archive reports](https://almanac.httparchive.org/en/2024/third-parties) median sites load 21 third-party resources totaling 450KB.

Third-party impact on INP happens three ways:

**Input delay**: User clicks while third-party code is executing. The browser queues the interaction until the main thread is free.

**Processing interference**: Your event handler runs, but third-party code also runs during the same task, extending total blocking time.

**Periodic execution**: Many scripts don't just run on load. Analytics libraries send beacons, ad scripts refresh, and chat widgets poll for new messages. These ongoing tasks compete with interactions throughout the session.

### Hidden cost: structured data & meta tags

We often think of SEO tags as "free," but injecting massive JSON-LD blobs or complex meta tags via JavaScript can block the main thread.

If you use a tag manager or a CMS plugin to inject Schema.org data:

1. The browser parses the script (Main Thread)
2. The browser serializes the JSON (Main Thread)
3. The browser inserts it into the DOM (Main Thread)

For large product catalogs, a 50KB JSON-LD object can cause a 100ms+ blocking task to "help SEO" - ironically hurting your Core Web Vitals ranking factor. Use the [JSON Size Analyzer](/tools/json-size) to measure Schema.org payloads before injection.

The Lighthouse audit "Reduce the impact of third-party code" identifies scripts blocking the main thread. It measures transfer size and main thread execution time per third-party origin.

## How to identify the issue

Open Chrome DevTools Performance panel, record an interaction, and look at the main thread. Third-party code appears as tasks from external domains. Sort the call tree by "Self Time" to find the worst offenders.

For ongoing monitoring, measure third-party main thread time:

```js
const observer = new PerformanceObserver((list) => {
  for (const entry of list.getEntries()) {
    if (entry.duration > 50) {
      const script = entry.attribution?.[0]?.containerSrc
      if (script && !script.includes(location.hostname)) {
        console.log('Third-party long task:', script, entry.duration)
      }
    }
  }
})
observer.observe({ entryTypes: ['longtask'] })
```

## The fix

### Load non-essential scripts on interaction

The facade pattern shows a lightweight placeholder, then loads the full script when users interact. YouTube embeds are the classic example - a thumbnail and play button cost a few KB, while the full player loads 1MB+.

```js
class YouTubeFacade extends HTMLElement {
  connectedCallback() {
    const videoId = this.getAttribute('video-id')
    this.innerHTML = `
      <img src="https://i.ytimg.com/vi/${videoId}/hqdefault.jpg"
           alt="Video thumbnail" loading="lazy">
      <button aria-label="Play video">Play</button>
    `
    this.querySelector('button').onclick = () => this.loadPlayer()
  }

  loadPlayer() {
    const videoId = this.getAttribute('video-id')
    this.innerHTML = `
      <iframe src="https://www.youtube.com/embed/${videoId}?autoplay=1"
              allow="autoplay; encrypted-media" allowfullscreen></iframe>
    `
  }
}
customElements.define('youtube-facade', YouTubeFacade)
```

Apply this pattern to chat widgets, social embeds, maps, and any interactive third-party content below the fold.

### Use `async` or `defer` appropriately

Both attributes prevent render blocking, but they execute differently:

- `async` - Downloads in parallel, executes immediately when ready. Order not guaranteed.
- `defer` - Downloads in parallel, executes after HTML parsing, in document order.

```html
<!-- Analytics: doesn't need DOM, order doesn't matter -->
<script async src="https://analytics.example.com/track.js"></script>

<!-- Widget that needs DOM: defer ensures HTML is ready -->
<script defer src="https://widget.example.com/embed.js"></script>
```

For scripts that must execute in order (like GTM loading other scripts), use `defer`. For independent analytics, use `async`.

### Self-host critical third-party code

External scripts require DNS lookup, TCP connection, and TLS handshake - adding 100-500ms before download starts. Self-hosting eliminates this latency.

```bash
curl -o public/gtm.js https://www.googletagmanager.com/gtm.js?id=GTM-XXXX
```

Self-hosted scripts also benefit from your CDN, HTTP/2 multiplexing, and caching strategy. Update periodically to get vendor fixes.

Be aware: some scripts check their origin or rely on same-origin cookies. Test thoroughly before self-hosting.

### Use web workers for analytics

Web Workers run JavaScript off the main thread. Tools like [Partytown](https://partytown.builder.io/) proxy third-party scripts into a worker:

```html
<script>
  partytown = {
    forward: ['dataLayer.push', 'gtag']
  }
</script>
<script src="/~partytown/partytown.js"></script>
<script type="text/partytown" src="https://www.googletagmanager.com/gtag/js"></script>
```

The worker communicates with the main thread through postMessage, but third-party execution no longer blocks interactions. [Early adopters report 50-70% TBT reduction](https://partytown.builder.io/).

Caveats: not all scripts work in workers (DOM access requires proxying), and debugging becomes harder. Test critical user flows.

### Set resource hints

Preconnect warms up connections to third-party origins you know you'll need:

```html
<link rel="preconnect" href="https://www.google-analytics.com">
<link rel="dns-prefetch" href="https://static.ads.example.com">
```

Use `preconnect` for critical third parties (2-3 max - more wastes bandwidth). Use `dns-prefetch` for less critical origins where you want DNS resolved early but don't need the full connection.

## Framework-specific solutions

<callout icon="i-logos-nextjs-icon">

**Next.js**

The `next/script` component provides loading strategies:

```jsx
import Script from 'next/script'

export function GoogleTagManager() {
  return <Script src="https://www.googletagmanager.com/gtag/js" strategy="worker" />
}

export function ChatWidget() {
  return <Script src="https://widget.example.com/chat.js" strategy="lazyOnload" />
}

export function Analytics() {
  return <Script src="https://analytics.example.com/track.js" strategy="afterInteractive" />
}
```

</callout>

<callout icon="i-logos-nuxt-icon">

**Nuxt**

Use `useScript` composable with triggers:

```ts
// Load when browser is idle
useScript('https://analytics.example.com/track.js', {
  trigger: 'idle'
})

// Load when element is visible
const chatContainer = ref(null)
useScript('https://chat.example.com/widget.js', {
  trigger: useScriptTriggerElement({ el: chatContainer })
})

// Load on manual trigger
const { load } = useScript('https://maps.example.com/api.js', {
  trigger: 'manual'
})
// Later: load()
```

For worker-based loading, use `@nuxtjs/partytown` module.

</callout>

## Verify the fix

After changes, measure the impact:

1. Run Lighthouse - check "Third-party code blocked the main thread" diagnostic
2. Record a Performance trace during interaction - third-party tasks should be shorter or absent
3. Monitor RUM data - compare INP before and after deployment

<audit-impact current-value="350" metric="tbt" target-value="100">



</audit-impact>

Expect TBT reduction of 100-500ms when moving analytics to workers or deferring heavy embeds.

## Common mistakes

**Loading everything on page load**: Even deferred scripts execute eventually. Use facades and lazy loading for content users might never interact with.

**Preconnecting to too many origins**: Each preconnect consumes bandwidth and CPU. Limit to 2-3 critical origins.

**Breaking tracking**: Deferred analytics may miss early page events. Make sure critical tracking fires before users can navigate away.

**Testing only on fast connections**: Third-party impact is far worse on mobile. Test on throttled connections.

## Third-party triage

Not all third parties are equal. Prioritize by business impact:

<table>
<thead>
  <tr>
    <th>
      Category
    </th>
    
    <th>
      Priority
    </th>
    
    <th>
      Strategy
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      Consent management
    </td>
    
    <td>
      Critical
    </td>
    
    <td>
      Load early, minimize size
    </td>
  </tr>
  
  <tr>
    <td>
      Analytics
    </td>
    
    <td>
      High
    </td>
    
    <td>
      Defer or worker
    </td>
  </tr>
  
  <tr>
    <td>
      A/B testing
    </td>
    
    <td>
      High
    </td>
    
    <td>
      Load early, minimize blocking
    </td>
  </tr>
  
  <tr>
    <td>
      Ads
    </td>
    
    <td>
      Revenue
    </td>
    
    <td>
      Lazy-load when in view
    </td>
  </tr>
  
  <tr>
    <td>
      Chat widgets
    </td>
    
    <td>
      Medium
    </td>
    
    <td>
      Facade, load on interaction
    </td>
  </tr>
  
  <tr>
    <td>
      Social embeds
    </td>
    
    <td>
      Low
    </td>
    
    <td>
      Facade with placeholder
    </td>
  </tr>
  
  <tr>
    <td>
      Video embeds
    </td>
    
    <td>
      Low
    </td>
    
    <td>
      Facade with thumbnail
    </td>
  </tr>
</tbody>
</table>

Consider removing scripts that don't justify their performance cost. Every analytics tool added has a cumulative impact.

## Related issues

Third-party scripts often combine with other INP causes:

- [Long-Running JavaScript](/learn-lighthouse/inp/long-running-javascript) - Your code + third-party code compete
- [Hydration Issues](/learn-lighthouse/inp/hydration-issues) - Scripts loading during hydration extend blocking time

## Test your entire site

Third-party impact varies by page. A minimal homepage may perform well while product pages with multiple embeds suffer. [Unlighthouse](/) scans every page and surfaces TBT problems from third-party scripts across your entire site.
