Skip to main content
Beta: Front-End Checklist is currently in beta. Some issues are still being fixed. Thanks for your patience.

Optimize third-party script loading

Load third-party scripts asynchronously to prevent blocking the main thread and improve page performance.

Utilities
Quick take
Typical fix time 20 min
  • Use async for independent scripts (analytics, ads)
  • Use defer for DOM-dependent scripts (chat widgets, social)
  • Lazy-load non-critical scripts after page load
  • Consider self-hosting critical third-party scripts
Why it matters: Third-party scripts are the #1 cause of slow websites—unoptimized loading can add 2-5 seconds to your page load time.

Rule Details

Third-party scripts are often the most expensive JavaScript on the page and the least under your control. The right strategy is usually not "load it faster", but "load it later, conditionally, or not at all".

Code Examples

Never Block the Head with Optional Vendors

<!-- Bad: parser-blocking third parties -->
<head>
  <script src="https://analytics.example.com/track.js"></script>
  <script src="https://chat.example.com/widget.js"></script>
  <script src="https://reviews.example.com/embed.js"></script>
</head>
 
<!-- Better: independent scripts do not block parsing -->
<head>
  <script src="https://analytics.example.com/track.js" async></script>
  <script src="https://reviews.example.com/embed.js" defer></script>
</head>

Load Optional Vendors on Intent

<button id="open-chat">Chat with support</button>
 
<script>
const chatButton = document.querySelector('#open-chat')
 
chatButton.addEventListener('click', async () => {
  chatButton.disabled = true
  chatButton.textContent = 'Loading chat...'
 
  const script = document.createElement('script')
  script.src = 'https://chat.example.com/widget.js'
  script.async = true
  document.head.appendChild(script)
}, { once: true })
</script>

Use a Facade for Heavy Embeds

<button class="video-facade" data-video-id="abc123">
  <img src="/thumbnails/abc123.jpg" alt="Play product demo">
  <span>Play video</span>
</button>
 
<script>
document.querySelector('.video-facade').addEventListener('click', async (event) => {
  const target = event.currentTarget
  const iframe = document.createElement('iframe')
  iframe.width = '560'
  iframe.height = '315'
  iframe.src = `https://www.youtube.com/embed/${target.dataset.videoId}?autoplay=1`
  iframe.title = 'Product demo'
  iframe.allow = 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture'
  target.replaceWith(iframe)
}, { once: true })
</script>

React Idle or Interaction Loading

import { useEffect } from 'react'
 
function useThirdPartyScript(src: string, loadOn: 'idle' | 'interaction') {
  useEffect(() => {
    const loadScript = () => {
      const script = document.createElement('script')
      script.src = src
      script.async = true
      document.head.appendChild(script)
    }
 
    if (loadOn === 'idle') {
      if ('requestIdleCallback' in window) {
        requestIdleCallback(loadScript)
      } else {
        setTimeout(loadScript, 1500)
      }
      return
    }
 
    document.addEventListener('click', loadScript, { once: true })
    return () => document.removeEventListener('click', loadScript)
  }, [src, loadOn])
}

Why It Matters

  • Network contention: vendor scripts can crowd out CSS, fonts, and hero media during the most sensitive part of the load.
  • Main-thread blocking: even async scripts still need parsing and execution time, which can hurt Total Blocking Time and INP.
  • Unclear business value: many integrations run for every visitor even though only a fraction ever use them.
  • Compound cost: a single embed may bring extra JavaScript, images, fonts, cookies, and follow-up network requests.

Choose the Loading Strategy by Business Criticality

Use PageSpeed Insights (opens in new tab) or a trace to decide which vendors really belong in the initial route, because the right strategy depends on the measured cost of each script, not just on how important the integration feels.

Vendor typeRecommended strategyWhy
Bot detection, consent, critical fraud checksLoad before or around interactivity only if the page truly cannot function without itThese may be required for legal or security reasons
Analytics and tag managersasync, defer, or framework afterInteractiveImportant, but rarely worth blocking first paint
Chat, reviews, social embeds, maps, video playersIdle, visibility, or interaction-triggered loadingMost users do not need them on first paint
Ads and experimentation toolsLoad after critical content, and only where business requirements justify the costThese often carry large execution and network overhead

Framework Examples

Next.js

import Script from 'next/script'
 
export default function App() {
  return (
    <>
      <Script
        src="https://analytics.example.com/track.js"
        strategy="afterInteractive"
      />
 
      <Script
        src="https://chat.example.com/widget.js"
        strategy="lazyOnload"
      />
    </>
  )
}

Common Mistakes

  • Treating every vendor as critical: most third parties should not compete with LCP resources.
  • Using async as a complete fix: download order may improve, but execution cost remains.
  • Loading chat, video, or review widgets for every visitor: these are usually better behind idle, visibility, or interaction.
  • Skipping reserved space for deferred embeds: lazy-loading without dimensions can cause CLS.
  • Ignoring removal: sometimes the correct optimization is deleting or replacing the integration.

Practical Budgets

  • Keep blocking third-party scripts at 0 for normal content pages.
  • Keep pre-interactive third parties to the small set that is legally or functionally required.
  • Treat any single third-party script above roughly 100 KB transferred or any long task above 50ms as a candidate for deferment, facades, or removal.

Verification

Re-check the page with Lighthouse (opens in new tab) or a third-party-code trace after every vendor change so you can confirm the route actually got lighter.

Automated Checks

  • Inspect the network waterfall and confirm optional third parties start after critical CSS, fonts, and LCP resources, not before them.
  • Record a performance trace and verify third-party execution does not create long tasks above roughly 50ms during the initial route load.
  • Measure the page on a throttled mobile profile and confirm LCP, TBT, and INP improve or at least do not regress after the loading changes.

Manual Checks

  • Confirm interaction-triggered or idle-loaded vendors still work correctly when invoked and do not shift layout unexpectedly.

Use with AI

Copy these prompts to use with your AI assistant, or install the MCP server to use directly from Claude, Cursor, or Windsurf.

Check

Verify implementation

Analyze third-party scripts on this page to ensure they use async/defer and don't block rendering.

Fix

Auto-fix issues

Add async or defer attributes to third-party scripts and consider lazy loading non-critical scripts.

Explain

Learn more

Explain the performance impact of third-party scripts and strategies to minimize their blocking effect.

Review

Code review

Review the routes, assets, and loading behavior that affect Optimize third-party script loading. Flag exact files, requests, or rendering steps that add unnecessary network, CPU, or layout cost, and describe the measurement method used to confirm the issue.

Sources

References used to support the guidance in this rule.

Further Reading

Tools and supplementary material for exploring the topic in more depth.

Rules that often go hand-in-hand with this one.

Use resource hints for faster loading

Implement preload, prefetch, and preconnect hints to optimize resource loading priority.

Performance
Implement lazy loading for offscreen content

Images and heavy resources below the fold are lazy loaded to improve initial performance.

Performance
Load non-critical code on user interaction

Defer JavaScript modules, widgets, and third-party code until the user signals intent through a click, focus, hover, or similar interaction.

Performance
Load scripts with defer, async, or type=module

Prevent JavaScript from blocking HTML parsing by using defer, async, or type=module attributes on script tags so the browser can continue building the DOM while scripts download.

HTML

Was this rule helpful?

Your feedback helps improve rule quality. This stays internal for now.

Loading feedback...
0 / 385