/ JS Checker Fixes / Third-Party Tags

How to Fix Third-Party Tags

Third-party tags are the slow-creep performance killer. Marketing installs one analytics pixel, then a chat widget, then a heatmap tool, then an A/B testing script — each justified individually but together adding hundreds of KB and 1-2 seconds of execution. Median sites carry 20-30 third-party scripts. The fix is audit, defer, lazy-load, and set a budget that someone owns. This guide walks through each step plus the GTM-specific patterns that compound quickly.

1. Audit current tags

Step 1
List every third-party domain
DevTools → Network tab → filter to JS → reload. Sort by Domain. Every domain that isn't yours is a third-party. Common list: googletagmanager.com, google-analytics.com, intercom.io, hotjar.com, segment.com, mixpanel.com, cdn.amplitude.com, etc.
Step 2
Get size and timing per tag
Network panel shows transfer size, decoded size, and start/end timing per request. The "third-party usage" Lighthouse audit summarises this in one view with grand totals.
Step 3
Cross-reference with business value
For each tag, ask the owner: "What business decision did this drive in the last 90 days?" Tags that can't answer are candidates for removal. Typical audit cuts 30-40% of tags.

2. Move tags to async or defer

<!-- Before — synchronous, blocks render -->
<script src="https://cdn.heatmap.com/tracker.js"></script>
<script src="https://cdn.chat.com/widget.js"></script>

<!-- After — non-blocking -->
<script async src="https://cdn.heatmap.com/tracker.js"></script>
<script defer src="https://cdn.chat.com/widget.js"></script>

See the render-blocking JS guide for the rules of async vs defer.

3. Lazy-load heavy widgets

Chat widgets and similar large scripts shouldn't load on initial page load — they should load when user shows intent.

Pattern 1: Load on first user interaction

// Idle imports — pre-fetches without blocking
function lazyLoadChat() {
  const script = document.createElement('script');
  script.src = 'https://widget.intercom.io/widget/APP_ID';
  script.async = true;
  document.body.appendChild(script);
  // Remove the trigger so we don't load twice
  removeTriggers();
}

function removeTriggers() {
  ['mousemove', 'touchstart', 'click', 'scroll'].forEach(evt => {
    document.removeEventListener(evt, lazyLoadChat);
  });
}

['mousemove', 'touchstart', 'click', 'scroll'].forEach(evt => {
  document.addEventListener(evt, lazyLoadChat, { once: true, passive: true });
});

Pattern 2: Load after timeout

// Idle 5s after page load
window.addEventListener('load', () => {
  setTimeout(lazyLoadChat, 5000);
});

Pattern 3: Load on visible CTA click

// Visible support button with placeholder; loads widget on click
document.querySelector('.support-button').addEventListener('click', async () => {
  await loadChat();
  // Open the widget once loaded
  if (window.Intercom) window.Intercom('show');
});

4. Audit your GTM container

Google Tag Manager is the most common culprit — a single GTM container can hold dozens of tags, all firing on All Pages by default.

Step 1
List all tags in the container
GTM → Container → Tags. Each tag has a trigger. Audit each:
  • What does it fire on? "All Pages" is the most expensive trigger.
  • What does it do? Analytics ping, ad pixel, custom JavaScript.
  • Who owns it? Is anyone using its data?
Step 2
Tighten triggers
Tags should fire on the specific event that matters, not All Pages:
  • Conversion pixel: fire on /thank-you page only
  • Cart add tracking: fire on add-to-cart event only
  • Form submission: fire on form submit event only
  • Page view ping: All Pages is correct here
Step 3
Set Consent Mode V2
For marketing and advertising tags, configure them to wait for user consent before firing. GTM Settings → Consent → assign tags to advertisement/analytics consent. Tags don't fire until consent state is granted.

5. Consider tag-manager alternatives

For sites where GTM has become a bottleneck:

6. Self-host where you can

Some third-party services let you self-host their scripts. Pros: cached with your domain, served with your headers, no DNS lookup. Cons: must update periodically.

# Build script: fetch latest GA4 script and serve from your domain
curl -o public/js/gtag.js https://www.googletagmanager.com/gtag/js?id=G-XXXX

# Then in HTML
<script async src="/js/gtag.js"></script>

Some scripts (Stripe.js, recaptcha) require their CDN for security and version pinning. Don't self-host those.

7. Set a performance budget

# Lighthouse CI
{
  "ci": {
    "assert": {
      "assertions": {
        "third-party-summary": ["error", { "maxNumericValue": 300 }],
        "resource-summary:third-party:size": ["error", { "maxNumericValue": 250000 }]
      }
    }
  }
}

Budget: max 300ms total third-party blocking time, max 250 KB third-party transfer. Builds that exceed fail. The budget creates an enforced ceiling — adding a new tag means removing an old one or finding 50 KB elsewhere.

💡 The third-party-tag review is uncomfortable but high-leverage. One quarterly meeting auditing every tag's business value typically removes 30-40% and adds a few months of headroom before the next bloat cycle. Document each tag's purpose and decision rights — future-you will thank past-you.

⚙️ Re-run the JS Checker

Verify third-party tag impact has reduced.

Run JS Checker →
Related Guides: JS Checker Fixes  ·  Fix Render-Blocking JS  ·  Fix JS Bundle Size  ·  JS Checker Guide
💬 Got a problem?