Updated after a LinkedIn discussion. I reworked and expanded this article based on the discussion underneath it. Thanks to everyone who jumped in.
Site speed stopped being a developer concern and became a business one. It shows up directly in revenue: Akamai data from 10 billion visits found that 53 % of mobile users abandon a site that takes over three seconds to load, and Google and Deloitte measured that improving load time by just a tenth of a second lifted retail conversions by 8.4 %.
“Speed” has two faces. One is how fast the page loads and renders (measured by LCP). The other is how fast the site responds when a user clicks (measured by INP).
GTM is the usual suspect here. You add it, the site slows down, the conclusion suggests itself. Except that conclusion is usually wrong. And occasionally right. Let’s untangle it.
GTM takes the blame. But it only sometimes deserves it.
An empty Google Tag Manager (GTM) container downloads, parses, and runs. The GTM library itself is roughly 33 KB compressed — less than an average SVG icon.
You could stop here and declare that GTM doesn’t slow your site. More precisely: the container itself doesn’t. However:
- What you put in GTM has a huge impact. If you load Google Analytics 4 (GA4) and nothing else, the effect is likely minimal. Add Meta pixel, Google Ads, Sklik, Hotjar, and a pile of other tools, and you’re looking at a significant slowdown. And the impact of those tools is usually far bigger than the GTM script itself.
- The way you use GTM can slow the site down too. With large containers built up over years, I see it regularly. The more hands that touched the container over time, the more likely something inside is eating performance.
I won’t dwell on the first point — deploying a pile of platforms is a business (or marketing) decision you have to accept. But it’s worth breaking down how to actually use GTM.
Page load — when size matters
Let’s look at two basic metrics — how big a script is, and how much time it spends on the browser’s main thread.
| Script | Size (gzip) | Main-thread time |
|---|---|---|
| GA4 (gtag.js) | ~135 KB | 108 ms |
| Meta pixel | ~98 KB | 514 ms |
| Hotjar | ~60 KB | 732 ms |
| Microsoft Clarity | ~25 KB | 405 ms |
| LinkedIn Insight Tag | ~10 KB | 133 ms |
Size: measured directly from production scripts (gzip transfer), June 2026. Main-thread time: the median time a script spends on the main thread across all sites where it actually runs — the third-party-web dataset (Patrick Hulce), built on HTTP Archive data from Lighthouse measurements of millions of sites, 2024 snapshot.
Notice the mismatch. GA4 is the largest in the table, yet it loads the main thread the least. Clarity is the smallest, and takes nearly 4× as much as GA4. A script’s size and its impact on speed are unrelated.
Why size matters so little: GTM and tags load asynchronously and at low network priority. An async script doesn’t block HTML parsing — the browser fetches it on the side and keeps building the page, even with the snippet sitting in the head. It’s not entirely free, though: tags compete for bandwidth with critical resources, and once downloaded their code runs on the main thread. A lot of tags on a slow device can delay rendering — but that’s a second-order effect. The real hit to LCP comes in two cases: when someone drops a synchronous script (without the async attribute) into the head, blocking page parsing, and with A/B testing tools that use an anti-flicker snippet, which deliberately hides the page until the test loads.
What matters is runtime — how long the code runs on the main thread and blocks it. And size doesn’t help here: even a one-kilobyte script can freeze the browser with one badly written loop. A large library that spreads its work out over time may not hurt at all.
By the way, I deliberately left GTM out of the table. In this dataset it actually ranks among the worst — over 1000 ms — but that’s misleading. GTM is a “loader”: its number includes everything you fire through it. That’s not a property of GTM, it’s the bill for the tags inside. Which is exactly the point of this article.
One more note: TBT (Total Blocking Time) sums up all main-thread blocking during load — including the kind broken into small chunks. But fragmented blocking often doesn’t bother the user at all. What bothers them is a continuous freeze exactly when they want to click. And TBT can’t see that.
GTM and how fast your site responds
How fast a site responds to user actions is described by INP (Interaction to Next Paint). It measures something simple: you click (or tap, or press a key) and INP measures how long it takes the site to visibly respond. The fewer milliseconds, the snappier the site feels. A response under 200 ms counts as good.
And it’s not a fringe issue: in 2024 only 74 % of mobile sites had good INP versus 97 % on desktop. So it’s mainly a mobile problem — and that’s where GTM and tags hurt most.
Why do I talk about INP rather than TBT, which you might know from PageSpeed Insights? Because they measure different things. TBT is calculated in a lab — Google loads your page on a simulated device and counts blocking during load. INP, on the other hand, is measured on real visitors (Chrome collects it directly). And that’s what reveals the truth: people often click while the page is still loading, when tags are keeping the browser busy — and a lab test misses that. web.dev puts it bluntly: TBT is not a substitute for INP. You can have great TBT and still a site that drags under your fingers.
That INP isn’t just numbers for Google is shown by case studies: redBus improved INP by 72 % — and its sales rose 7 %. Economic Times cut main-thread blocking — and its bounce rate dropped by half.
How GTM hurts INP
Loading GTM and tags at the start barely affects INP — the browser gets that out of the way when the page starts. What hurts response to clicks is something else: tags that install “watchers” across the whole page — on every click, scroll, or mouse move. Auto-event tracking, some heatmaps, and session recorders do this routinely. Such a watcher doesn’t run once. It fires on every interaction and inserts its code between your click and the site’s response — right into the window INP measures. The more such watchers on the page, the lazier every further click. So it’s not so much about GTM’s size as about what the specific tags inside do once the site is running.
And what about GTM’s own built-in triggers — Click and Form Submission? They watch the whole page too, but do it efficiently: GTM doesn’t attach a listener to every element, just a single one on the document. The listener itself is light — it only reports that a click or form submit happened. What loads INP is what you hang on that event, and one specific trigger: “Click – All Elements” reacts to literally every click anywhere on the page. How to keep the built-in triggers in check, I cover in the next chapter.
How to keep GTM fast
There are a few techniques for making GTM as fast as possible.
Custom JavaScript variables that recompute over and over. Every custom JS variable in GTM is a function the browser evaluates synchronously on the main thread — again, every time something references it. The variable’s size doesn’t matter. Two things hurt. First, chaining: variable A reads variable B, which reads variable C — and every use of A runs the whole cascade again. Second, computationally heavy operations inside: a regex over long text, parsing a large JSON, walking the entire DOM or a growing dataLayer. On a classic site this gets lost, but in a single-page app the dataLayer grows with every page view — and the same variables and tags walk it again and again. Real-world measurements show how this pushes individual tag execution from tens of milliseconds into the hundreds, even seconds. And by the way — the number of variables isn’t the problem; have thousands of them. What matters is what they do.
Ask developers for clean data. The ideal looks like this: the developer hands clean, complete data into the dataLayer, and in GTM you just read it without computing anything. Note: in practice that often doesn’t happen and you need to clean, filter, and derive data in GTM. Just do it as little as possible.
Measure interactions only after the response renders — if a developer passes you information about an interaction, defer the dataLayer.push() as late as possible (ideally after the response renders) — it saves tens of milliseconds of INP. The logic stays, the runtime work drops.
Templates, not Custom HTML. A Custom HTML tag is a chunk of code GTM injects into the page at runtime — and the browser has work to do every time it runs. A template does the same thing more cheaply: it’s prepared up front and GTM runs it in a controlled way. Templates also have template storage: you can store the result of a heavier computation and just read the value for the rest of the page’s life — so the computation runs only once. If a template exists for your tool (GA4, Meta pixel, Clarity, Hotjar — they all do), use it.
Use simple triggers. Every event push in the dataLayer runs through all active triggers, evaluates their conditions, and resolves the variables those conditions touch. The more events flowing and the heavier the variables in the conditions, the more work on every event. So build trigger conditions on values already in the dataLayer — not on variables that have to compute something.
Enable built-in click and form listeners deliberately. The “Click – All Elements” trigger captures literally every click on the page, so GTM evaluates something even for clicks you don’t measure at all. If links are enough for you, use “Just Links”; otherwise constrain the trigger with a condition (URL or CSS selector) to what you actually track. Google recommends this itself, anyway.
Don’t fire tags too early. Lots of tags fire on Page View, “because that’s how it’s always been.” But Page View happens before the page renders. Analytics, heatmap, and remarketing tags have no reason to start that early — DOM Ready is a sensible compromise. The exception is tags where send speed matters, typically transaction measurement.
Cleanup (paused tags, exceptions, unused variables). A paused or deleted tag is excluded from the container. But watch out for blocking via an exception: a tag that “never fires” thanks to a trigger exception still stays in the container — downloaded and compiled for nothing. If you don’t want a tag, pause or delete it — don’t block it with an exception. And unused variables stay in the compiled container too, so clean them out from time to time.
By the way — GTM has concerns beyond speed. On the legal side, the Hannover court ruling that GTM without consent violates GDPR goes into detail. And if you gate tag firing on consent, check the most common mistakes in cookie banner setup too.
Is there a problem? And how big?
First, find out whether there’s anything to fix at all. I built a playground for it at sandbox.sabatka.net/speed/start — it compares your configuration against an empty container running just a basic GA4 tag. You’ll see the relative difference. It’s not a precise benchmark, but you’ll immediately know whether the problem is negligible or not.
For a quick second opinion, use PageSpeed Insights — enter your URL and check whether it flags third-party script issues or poor INP. If not, don’t bother. Focus on something that actually helps, like content or conversions.
If you want to measure it yourself, set up realistic conditions:
Step 1: Copy your production container. Never test in production. Remove or deactivate production IDs (GA4 Measurement ID, Meta pixel ID, etc.) — otherwise you’ll send test data to live accounts, and GA4 doesn’t offer a clean way to filter that out retroactively.
Step 2: Set up test conditions. Open DevTools (F12) and set:
- Network → Disable cache (simulates a first visit)
- Performance → CPU throttling to 4x slowdown (an average Android, not your M3 MacBook)
- Network → Network throttling to Fast 4G
Your users on mobile are in exactly these conditions — or worse.
Warning: The same goes for the sandbox playground — never enter production IDs (GA4 Measurement ID, Meta pixel ID, etc.). It’s for orientation, not live data.
If the difference is small, you probably don’t need to go further. Clean up the container once in a while — remove unused tags, clean out orphaned variables, move analytics and remarketing tags from Page View to DOM Ready. That alone often does the job.

Lab note
This is the Observer Effect — a principle from quantum physics where the act of measuring influences what’s being measured. Every tag you add to measure your site slows down the very site you’re trying to measure. The more you measure, the slower your site gets.
Going deeper — Chrome DevTools
If every millisecond matters, or you want your container tuned to the max, open Chrome DevTools.
Step 1: Set up realistic conditions. Same as above — Disable cache, Network throttling to Fast 4G, CPU throttling to 4x slowdown.
Step 2: Measure baseline. Test the page with GTM loaded but empty (all tags paused). Record:
- TBT (Total Blocking Time) — how much the main thread blocks during load
- LCP (Largest Contentful Paint) — when the largest visible element renders
- Waterfall in the Network tab — load order and duration of scripts
Step 3: Measure interaction too, not just load. This is the part most tests skip. After load, perform typical interactions — click into the menu, open a filter, add to cart — and in the Performance tab watch how long the main thread runs between the click and the repaint. This is where the global listeners that wreck INP show up. A recording of a real interaction tells you more than any lab number.
Step 4: Add tags incrementally. Activate tags one by one, measure both load and interaction each time, log the delta. It’s the only way to know which specific script is responsible for what.
At this level, structural changes start to make sense: rewrite Custom HTML tags as templates, lighten heavy variables, break up long main-thread tasks, or move to server-side tracking which shifts part of the processing from the browser to a server.
For common tracking mistakes found in these audits, see my MeasureCamp Czechia presentation.
Bottom line
The GTM container itself doesn’t slow your site in any dramatic way — 33 KB and done. What actually slows it down is the container’s contents — the tags you load through it. The way you use GTM has its share too: how you set up tags, triggers, and variables. And what decides the impact isn’t script size, as people often claim. It’s what the code does at runtime, and how fast the site responds to a click.
And one last thing to set straight — SEO. Core Web Vitals are one signal Google weighs, not the dominant factor. Relevant content ranks even on a slower site. Speed won’t save weak content — but with otherwise comparable content it tips the balance, and above all it affects your conversions and bounce rate. Which in the end matters more than your position in the results.
The approach stays the same: measure Core Web Vitals before you start optimizing. Fastest via PageSpeed Insights, long-term on real users in Search Console; other tools are summarized in this overview.
Test your site at sandbox.sabatka.net/speed/start. If you don’t know what to do with the results, or want help auditing your container — get in touch.
