# Third-party script impact on main thread is within budget

- **Pattern:** `ab-001794` (`marketing-page-speed.loading-strategy.third-party-impact`)
- **Severity:** high
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-001794
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-001794)

## Why it matters

Marketing pages accumulate third-party scripts faster than any other page type — analytics, heatmapping, chat, A/B testing, and advertising pixels each run JavaScript on the main thread, and their combined effect on ISO-25010 resource-utilisation is additive. Five third-party script domains loading synchronously or with `beforeInteractive` strategy can contribute 300–500ms of main thread blocking time on a mid-range Android device, which directly causes INP failures above the 200ms threshold. Beyond user experience, each additional third-party domain adds a DNS lookup and TLS handshake — typically 100–300ms per new origin — before any bytes are received. Google's CrUX data consistently shows that pages with four or more synchronous marketing scripts have significantly worse INP distributions.

## Severity rationale

High because synchronous third-party marketing scripts collectively cause main thread blocking that exceeds Google's INP threshold on mid-range mobile, degrading Core Web Vitals and page experience rankings.

## Remediation

Consolidate all marketing pixels into Google Tag Manager so they fire through a single script tag, then defer GTM itself with `afterInteractive`. Load the chat widget on-demand rather than on page load.

```tsx
import Script from 'next/script'
import { useState } from 'react'

// One tag for all pixels — consolidate in GTM dashboard
<Script
  src="https://www.googletagmanager.com/gtm.js?id=GTM-XXXXX"
  strategy="afterInteractive"
/>

// Chat widget — load only when user signals intent
const [chatReady, setChatReady] = useState(false)
const initChat = () => !chatReady && setChatReady(true)

{chatReady && (
  <Script src="https://cdn.intercom.com/widget.js" strategy="lazyOnload" />
)}
<button onMouseEnter={initChat} onClick={initChat}>Chat with us</button>
```

Aim for no more than 3 third-party script domains on initial page load. Run `npx lighthouse --output=json` and check the "Third-Party Summary" section for main thread blocking time per origin.

## Detection

- **ID:** `third-party-impact`
- **Severity:** `high`
- **What to look for:** Marketing pages commonly accumulate many third-party scripts: analytics (GA, Plausible, Fathom), heat mapping (Hotjar, FullStory), chat (Intercom, Drift, Crisp), A/B testing (Optimizely, VWO), ad pixels (Meta Pixel, LinkedIn Insight, Google Ads). Each adds main thread blocking time. Check: (1) how many third-party script domains are loaded, (2) whether they use async/defer/afterInteractive, (3) total estimated script payload from third parties > 200KB is a concern. Count `<Script>` components and `<script>` tags in layout files. Count all instances found and enumerate each.
- **Pass criteria:** Third-party scripts collectively add fewer than 150ms of main thread blocking time. This is achievable by: loading all analytics via `afterInteractive`, using a tag manager to consolidate multiple pixels, and not loading chat widgets until user interaction.
- **Fail criteria:** More than 3 third-party script domains loaded synchronously or with `beforeInteractive`. Combined third-party JS > 300KB. Chat widget loaded immediately rather than on first interaction.
- **Skip (N/A) when:** No third-party scripts detected in the project.
- **Detail on fail:** `"5 third-party script domains detected: GTM, Hotjar, Intercom, Meta Pixel, LinkedIn Insight. Intercom loaded synchronously. Combined payload estimated >400KB. Main thread blocking time likely exceeds 300ms on mobile."`
- **Remediation:** Consolidate and defer third-party scripts:

  ```tsx
  // Preferred: route all pixels through Google Tag Manager
  // One script tag, fire all pixels from GTM
  <Script
    src="https://www.googletagmanager.com/gtm.js?id=GTM-XXXXX"
    strategy="afterInteractive"
  />

  // Chat — load only on first interaction (massive saving)
  const [chatLoaded, setChatLoaded] = useState(false)
  const loadChat = () => {
    if (!chatLoaded) {
      setChatLoaded(true)
      // dynamically inject Intercom script
    }
  }
  <button onClick={loadChat} onMouseEnter={loadChat}>Chat with us</button>
  ```

## External references

- iso-25010 performance-efficiency.resource-utilization

Taxons: performance

HTML version: https://auditbuffet.com/patterns/ab-001794
