What tags actually cost your page

Tags are snippets of third-party code inserted into a site, typically via a tag manager, and are most commonly used for marketing and analytics. Their performance impact varies wildly between sites. A tag manager is essentially an envelope: it provides the vessel, but what you fill it with and how you use it is mostly up to you.

While the techniques below reference Google Tag Manager, most of the ideas apply equally to other tag managers.

How tags hit your Core Web Vitals

Tag managers most often affect Core Web Vitals indirectly by consuming the resources needed to keep a page fast and responsive. Bandwidth goes toward downloading the tag manager's JavaScript and the subsequent calls it makes. CPU time on the main thread is spent evaluating and executing both the tag manager's own code and the code of every tag it fires.

Largest Contentful Paint (LCP) is vulnerable to bandwidth contention during the critical loading window, and main-thread blocking can delay the LCP render time. Cumulative Layout Shift (CLS) can suffer either from delayed loading of critical resources before first render or from tags injecting content into the page. Interaction to Next Paint (INP) is sensitive to main-thread CPU contention, and there is a observed correlation between larger tag managers and poorer INP scores.

Pick the right tag type for the job

Performance impact differs by tag type. Image tags (pixels) are generally the most performant, followed by custom templates, and finally custom HTML tags. Vendor tags vary depending on what functionality they allow.

Usage matters as much as type. Pixels perform well largely because the tag type imposes tight restrictions on how they can be used. Custom HTML tags are not inherently bad, but the freedom they give users makes them easy to misuse in ways that hurt performance. Also keep scale in mind: the impact of a single tag may be negligible, but it becomes significant when tens or hundreds fire on the same page.

Some scripts should never ride a tag manager

Tag managers are rarely the right delivery mechanism for resources that implement immediate visual or functional aspects of the user experience—cookie notices, hero images, or core site features. Loading these via a tag manager typically delays their arrival, which hurts UX and can inflate metrics such as LCP and CLS. Additionally, some users block tag managers, so relying on one for core UX features risks breaking the site for those users.

Proceed with caution on Custom HTML tags

Custom HTML tags are heavily used across the web. Despite the name, they are primarily a way to add custom <script> elements to a page, with very few restrictions on what you can do. Their performance impact varies enormously. When measuring your site, note that most tooling attributes a Custom HTML tag's cost to the tag manager that injected it, rather than to the tag itself.

Creating a custom tag in Google Tag Manager

Custom HTML tags can also insert elements into the surrounding page, and that act of insertion is a common source of problems:

  • In most situations, inserting an element forces the browser to recalculate the size and position of every item on the page—a process known as layout. A single layout is cheap; doing it excessively is not. The impact is larger on lower-end devices and pages with a high number of DOM elements.
  • Inserting a visible element after the surrounding area has already rendered causes a layout shift. This isn't unique to tag managers, but because tags typically load later than other page parts, they are commonly inserted into the DOM after the surrounding page has rendered.

Prefer Custom Templates

Custom templates support many of the same operations as Custom HTML tags but are built on a sandboxed version of JavaScript. They provide APIs for common cases like script and pixel injection. A knowledgeable power user builds the template with performance in mind, and less technical users consume it—a much safer arrangement than handing out full Custom HTML access.

The tighter restrictions on custom templates make them far less likely to produce performance or security issues. Those same restrictions mean they don't work for every use case.

A custom template in Google Tag Manager

Inject scripts the right way

Injecting a script is a very common tag-manager use case. The recommended approach is a Custom Template using the injectScript API. For converting an existing Custom HTML tag, see Convert an existing tag.

If you must use a Custom HTML tag, remember these points:

  • Load libraries and large third-party scripts with a script tag—for example <script src="external-scripts.js">—that downloads an external file, rather than copy-pasting the whole script into the tag. Pasting the contents inline avoids a separate round-trip for the download, but it inflates the container size and prevents the browser from caching the script separately.
  • Many vendors advise placing their <script> tag at the top of the <head>. For scripts loaded through a tag manager this is often pointless, because by the time the tag manager executes, the browser has usually finished parsing the head.

Pixels: when a script just isn't needed

Third-party scripts can sometimes be replaced by image or iframe pixels. Pixels may support less functionality than script-based tags and are often treated as a lesser implementation, but inside a tag manager they can be dynamic—firing on triggers and passing different variables.

Pixels are the most performant and secure tag type, because no JavaScript executes after the pixel fires. Their resource size is under 1 KB and they cause no layout shifts. Check with your provider for pixel support; you can also inspect their code for a <noscript> tag, since vendors that support pixels often embed them there.

Custom image tag in Google Tag Manager

Alternatives worth knowing

Pixels became popular because they were once the cheapest, most reliable way to make an HTTP request when the server response is irrelevant—sending data to analytics providers, for instance. The navigator.sendBeacon() and fetch() keepalive APIs address the same case and are arguably more reliable.

Pixels remain perfectly fine—well supported and minimal in cost. But if you are building your own beacons, the modern APIs are worth considering.

sendBeacon()

The navigator.sendBeacon() API sends small amounts of data to web servers when the response does not matter.

const url = "https://example.com/analytics";
const data = JSON.stringify({
    event: "checkout",
    time: performance.now()
});

navigator.sendBeacon(url, data);

sendBeacon() is limited: it only makes POST requests and cannot set custom headers. It is supported by all modern browsers.

Fetch API keepalive

keepalive is a flag that lets the Fetch API make non-blocking requests such as event reporting and analytics, by passing keepalive: true in the parameters to fetch().

const url = "https://example.com/analytics";
const data = JSON.stringify({
  event: "checkout",
  time: performance.now()
});

fetch(url, {
    method: 'POST',
    body: data,
    keepalive: true
});

fetch() keepalive and sendBeacon() are very similar—in Chromium browsers, sendBeacon() is now built on fetch() keepalive. The choice comes down to the features and browser support you need: fetch() is far more flexible, but keepalive has less browser support than sendBeacon().

Know what your tags actually do

Tags are often created by following vendor-provided guidance. If a vendor's code is unclear, find someone who can explain it. A second opinion can reveal potential performance or security problems. In the tag manager, label every tag with an owner. When tags go unowned, no one dares remove them for fear of breaking something.

Choosing the right firing conditions

Trigger configuration is largely a matter of firing each tag only when it is needed, using an event that balances business requirements against execution cost. Triggers are themselves JavaScript, so they add to the container’s size and processing overhead. The cumulative effect of many click, timer, or scroll triggers can weigh down the tag manager even though each individual trigger is small.

Tags that load large resources or run lengthy scripts deserve the most attention here. In general, firing earlier in the page lifecycle has a greater performance impact, because resources are scarce during initial load. A page view trigger can be set to fire on Page load, DOM Ready, or Window Loaded. If a tag does not need to run during page load, schedule it after Window Loaded.

The gap between DOM Ready and Window Loaded can be quite long, which makes it hard to time tags precisely with the built-in page view triggers. For events that do not map cleanly to a built-in trigger, define a custom event trigger and update the relevant tags to use it.

Custom Event trigger in Google Tag Manager

Then push the corresponding event to the data layer to fire the trigger.

// Custom event trigger that fires after 2 seconds
setTimeout(() => {
  dataLayer.push({
    'event' : 'my-custom-event'
  });
}, 2000);

You can also restrict firing by adding conditions to a trigger. A simple and effective example is limiting a tag to pages where it is actually used, rather than letting it fire site-wide.

Trigger conditions in Google Tag Manager

Built-in variables can supply those conditions.

Because no trigger can fire before the tag manager itself loads, the load timing of the tag manager is just as important as individual trigger settings, and it affects every tag on the page. Delaying the tag manager load can prevent tags from accidentally running too early.

Keeping variables lean

Variables read data from the page for use in tags and triggers. They add JavaScript to the container, and some—particularly custom JavaScript variables—can be arbitrarily large. Variables are continually evaluated by the tag manager, so their count matters. Keep them minimal and delete outdated ones to shrink the container script and cut processing time.

Tag hygiene and the data layer

Centralizing page data in the data layer is strongly recommended. The data layer is a JavaScript array of objects holding the information to pass to Google Tag Manager; it can also drive triggers.

// Contents of the data layer
window.dataLayer = [{
    'pageCategory': 'signup',
    'visitorType': 'high-value'
  }];

// Pushing a variable to the data layer
window.dataLayer.push({'variable_name': 'variable_value'});

// Pushing an event to the data layer
window.dataLayer.push({'event': 'event_name'});

Putting data in one place gives third-party scripts a single, well-defined source, reducing redundant variable calculations and script execution. It also lets you control what data tags can access instead of granting direct JavaScript variable or DOM access. Updating the data layer does force Google Tag Manager to re-evaluate container variables and possibly fire tags, but performance problems that show up under data layer activity are usually symptoms of an inefficient container rather than the data layer itself.

Tags can end up duplicated if they appear both in the page HTML and are injected by the tag manager. Remove or pause tags that are no longer used rather than suppressing them with a trigger exception; pausing or removal drops the code from the container payload, while a trigger exception merely keeps the code in place. When you remove tags, audit their triggers and variables as well. Note that paused tags still contribute to container size, just less than active tags do.

For stricter control, configure allow and deny lists through the data layer.

window.dataLayer = [{
  'gtm.allowlist': ['<id>', '<id>', ...],
  'gtm.blocklist': ['customScripts']
}];

These lists can ban custom HTML tags, JavaScript variables, or direct DOM access, restricting implementations to pixels and predefined tags driven solely by the data layer. That restriction can yield a more performant, more secure container.

Server-side tagging is worth evaluating, particularly for larger sites. It moves vendor code off the client and shifts processing to the server. For example, with client-side tagging, sending data to multiple analytics endpoints means the client makes separate requests for each. Server-side tagging lets the client make a single request to a server-side container, which then forwards the data to each analytics account. Keep in mind that only certain tags work with server-side containers; compatibility varies by vendor. See An introduction to server-side tagging for details.

Container structure and size

Running multiple containers on one page is rarely a good idea. It duplicates core script execution and increases overhead. Effective exceptions exist, for example a lighter “early load” container paired with a heavier “later load” container, or a restricted container for less technical users alongside a more tightly managed container for complex tags. If multiple containers per page are unavoidable, follow Google Tag Manager’s guidance for setup.

Across sites and properties, container count is a workflow and performance consideration. A single container can cover multiple sites that are structurally similar. But a brand’s mobile and web apps, for instance, usually differ enough in structure to warrant separate containers. Stretching one container across dissimilar properties forces complex conditional logic that bloats the container.

Container size is a useful canary even if it should not be the primary optimization target. Google Tag Manager caps containers at 300 KB and issues a warning once a container reaches 70% of that limit. Trigger gremlins aside, most sites should stay well below it—the median container is around 50 KB, and the library itself runs about 33 KB compressed.

When creating a container version, give it a meaningful name and a short description of substantial changes. Those notes make future debugging of performance problems considerably easier.

Tag workflows and change management

Tags change over time, and unchecked changes can silently degrade page performance. Establishing a disciplined workflow for tag updates is just as important as the initial setup.

Test before you ship

Tags should go through the same kind of review that first-party code does. Before deployment, verify that the tag behaves correctly and check for the common ways tags can hurt performance:

  • Does the tag load resources, and are they reasonably sized?
  • Does it trigger layout shifts?
  • Does it run a script that takes too long?

Tag manager Preview mode lets you exercise a tag on the live site without making it public. It includes a debugging console that shows what each tag is doing.

Keep in mind that preview mode itself adds overhead: Google Tag Manager runs slightly slower because of the extra work needed to populate the debug console. For that reason, don't compare Web Vitals data collected in preview mode against production measurements. The behavior of the tags themselves is not affected by this discrepancy.

An alternative approach is standalone testing. You can build an empty test page that loads a container with only the tag you want to evaluate. This setup won't catch everything — for instance, it cannot reveal layout shifts that occur only in the context of the real page — but it makes it much easier to isolate and measure the tag's impact on script execution. The Telegraph has documented how it uses this isolation method to improve the performance of its third-party code.

Keep an eye on tag execution

Ongoing monitoring catches problems that appear after the tag is live. The Google Tag Manager Monitoring API reports execution time data for individual tags to any endpoint you designate. For a practical guide to building this, Simo Ahava's write-up on creating a Google Tag Manager monitor is a good starting point.

Require review for container edits

First-party code is normally reviewed before it deploys; third-party tags should get the same treatment. If your tag manager supports it, enabling two-step verification forces administrator approval before any container change is applied. Alternatively, container notifications email you about specific container events if you want visibility without blocking changes.

Audit your tags periodically

Tags tend to accumulate: teams add them for campaigns and experiments, but nobody remembers to remove them when the need passes. A periodic audit is the only reliable way to reverse that trend. How often you audit depends on how frequently your tags are updated.

Practical tips for auditing:

  • Label every tag with its owner so it is clear who can decide whether it is still necessary.
  • Review triggers and variables, not just tags — these can introduce performance problems too.

A deeper look at controlling third-party scripts is covered in the web.dev guide on keeping third-party scripts under control.