How to Reduce the Impact of Third-Party Code from Google Tag Manager

  • Performance

When you run a PageSpeed Insights report, one of the most common recommendations you'll see is:

"Reduce the impact of third-party code."

And if you're using Google Tag Manager (GTM), it's often the first culprit listed.

This can be frustrating โ€” GTM is essential for analytics, ads, and tag deployment, but it can also slow down your site's Largest Contentful Paint (LCP) and Time to Interactive (TTI) metrics.

The good news is that you can keep GTM and still improve performance by changing when it loads. Instead of running it as soon as the page begins to render, you can delay GTM until after a user interacts with the page โ€” significantly reducing its impact on your Core Web Vitals.

Why Google Tag Manager Hurts PageSpeed

When GTM loads immediately, it starts fetching and executing all your connected tags before the page is interactive. That means JavaScript execution time goes up, CPU work increases, and your main thread is blocked.

Even if your page looks visually ready, Google's metrics see it as slower because those background tasks delay meaningful paint and interactivity.

Typical Offenders Inside GTM

  • Google Analytics or GA4 tags
  • Google Ads conversion scripts
  • Hotjar, Meta Pixel, and other marketing scripts
  • Consent management tools

Each adds milliseconds โ€” or even seconds โ€” to your load time.

The Solution: Load GTM After User Interaction

The key insight:

You don't need GTM immediately when the page loads.

If you delay it until a user scrolls, taps, or presses a key, you can serve a near-instant page and still fire GTM before they ever engage meaningfully with content.

This pattern is sometimes called "interaction-triggered loading".

Here's the script we built and use on high-performance sites:

Deferred Google Tag Manager Loader

HTML
<!--
    Deferred Google Tag Manager loader

    Usage:
    1) Copy this snippet into your <head> (recommended) or just before </body>.
    2) Replace GTM-XXXXXXX in the `gtm.src` URL with your GTM container ID.
    3) Adjust triggers below (interaction events, timeout, or window load) to match privacy/consent needs.

    Notes:
    - This approach queues the dataLayer immediately so other scripts can push to it before GTM loads.
    - The loader waits for an early user interaction before fetching gtm.js to avoid blocking render and to improve LCP.
    - If you require explicit consent before loading analytics, replace the interaction listeners with your consent callback.
    - Default events used: scroll, pointerdown, touchstart, keydown. These are passive and registered once.
    - For debugging, temporarily set `loaded = true` to false or remove the `once` option to re-trigger.
-->
<script>
    // Ensure a global dataLayer exists immediately so other scripts can push events safely.
    window.dataLayer = window.dataLayer || [];

    (function () {
        // Prevent multiple loads
        var loaded = false;

        // Configure these values to suit your site / privacy model
        var GTM_ID = 'GTM-XXXXXXX'; // <-- Replace with your container ID
        var INTERACTION_EVENTS = ['scroll', 'pointerdown', 'touchstart', 'keydown'];

        function loadGTM() {
            if (loaded) return; // idempotent
            loaded = true;

            // GTM requires a bootstrap event on dataLayer before gtm.js is executed
            dataLayer.push({ 'gtm.start': Date.now(), event: 'gtm.js' });

            // Create and append the GTM script
            var gtm = document.createElement('script');
            gtm.async = true;
            gtm.src = 'https://www.googletagmanager.com/gtm.js?id=' + encodeURIComponent(GTM_ID);
            document.head.appendChild(gtm);
        }

        // Register interaction listeners (passive + once) for earliest non-blocking load
        var opts = { once: true, passive: true };
        INTERACTION_EVENTS.forEach(function (t) {
            window.addEventListener(t, loadGTM, opts);
        });

        // If you need to respect consent, replace the above listeners with your consent handler
        // e.g. onConsentGranted(() => loadGTM());
    })();
</script>

How It Works

  • โœ… Instant page render: The script doesn't block rendering because gtm.js isn't fetched right away.
  • โœ… Preserved dataLayer: We initialize window.dataLayer immediately, so any script pushing events before GTM loads won't break.
  • โœ… First interaction trigger: As soon as the user scrolls, taps, or presses a key, GTM loads asynchronously.
  • โœ… Better LCP & INP scores: By shifting the heavy lifting to post-interaction, your page's core metrics improve โ€” often by 10โ€“20 points in PageSpeed Insights.

Privacy & Consent Considerations

If your site requires cookie consent before analytics, this approach integrates naturally: simply replace the interaction listener with your consent callback. For example:

onConsentGranted(() => loadGTM());

This ensures GTM (and all tracking tags) load only after the user has granted permission โ€” satisfying both performance and compliance requirements.

When Not to Use This Approach

  • If you rely on GTM for critical functionality that must run on page load (e.g., A/B testing or essential consent banners).
  • If your site runs server-side tagging, which already minimizes the client-side load.
  • If your analytics must capture 100% of pageviews (you may lose data for users who bounce before interacting).

For everyone else, this is a smart, simple, and reversible optimization.

Final Thoughts

Modern websites need to balance speed, data, and compliance.

Google Tag Manager is powerful โ€” but when it loads too early, it hurts the very performance metrics Google uses to rank your site.

This lightweight deferred loader helps you keep GTM flexible without sacrificing performance.