On 3 March, a marketing coordinator added a heatmap tool to the tag manager container. No pull request, no release note, no performance budget check. Six weeks later the mobile checkout was failing Interaction to Next Paint and the support queue had a new complaint: the pay button takes two taps.
That is script drift. The page you signed off at launch is not the page your customers load today, even though the repository, the pipeline and the changelog all say nothing changed.
Drift is normal behaviour, not a mistake
Third-party code is designed to update itself. Vendor bundles get served from unversioned URLs, so analytics.js in October is not the file it was in January. A tag manager container holds dozens of tags, and each tag can pull its own dependencies, which pull theirs. Consent tooling wraps other scripts, A/B testing injects variant bundles, personalisation and chat widgets load per session, and the payment provider ships a new SDK when it feels like it.
The numbers are not marginal. The 2025 Web Almanac found 92 percent of pages load at least one third-party resource, and scripts account for 24.8 percent of all third-party requests, more than images, fonts or anything else. Most of that code runs on the main thread of a device you do not control.
None of it shows up in a code review, because it is not in your code.
The performance bill arrives as INP
Interaction to Next Paint replaced First Input Delay in March 2024 and measures the whole interaction, from the tap to the next frame painted. The threshold for "good" is 200 milliseconds at the 75th percentile. FID only asked how long the first click waited before the handler started. INP asks whether the browser could respond at all, across every interaction on the page.
Third-party JavaScript is the usual reason it cannot. The Chrome Aurora team measured a Google Tag Manager container with 18 tags and found it increased Total Blocking Time nearly twentyfold. The 2025 Web Almanac puts the mobile median TBT at 1,916 milliseconds, roughly ten times the 200 ms practitioners treat as the ceiling. Chrome UX Report data for August 2026 showed 85.3 percent of origins with good INP, down from 87.2 percent in March. It is the only Core Web Vital that got worse over the year, and the reason is not that sites got heavier in their own bundles. It is that the main thread is already busy with a chat widget when the customer taps Add to cart.
Lab scores hide this. Synthetic runs often block third-party requests outright or serve them from a cache, so a Lighthouse report can look clean while field data at p75 shows a template in the red. If the number you report comes from a lab, it is a hypothesis about performance, not a measurement of it.
Where it costs money
On a product listing page, a sluggish filter control turns browsing into a chore. On a checkout, the cost is easier to name: a button that does nothing for half a second gets tapped twice, and the second tap reads as a broken store. Customers do not file a bug, they leave.
Search is a slower bleed. Core Web Vitals work as a tiebreaker rather than a lever: a fast page will not outrank genuinely better content, but between two comparable pages the one with a healthy p75 wins the marginal position. AI answer engines add another filter. They fetch, parse and render candidate pages, and a template that needs two seconds of script execution before the content settles is a worse citation candidate than one that does not.
The honest version: performance work pays best where the page is already commercially sound. Fixing INP on a thin category page with duplicate descriptions buys very little.
The security argument is stronger than the SEO one
Web skimming attacks, usually filed under the Magecart label, inject JavaScript that copies card data as it is typed and posts it somewhere else. The purchase completes normally. The customer sees a receipt, the store sees an order, and the card details are already gone. In January 2026, Malwarebytes reported a skimming campaign reaching major payment networks through compromised storefronts, and the pattern is consistent across the last several years: the entry point is rarely the checkout code a developer wrote. It is a stale plugin, a compromised vendor bundle, or a tag nobody remembers approving.
This is why PCI DSS added two requirements that stopped being optional on 31 March 2025. Requirement 6.4.3 asks for a method to confirm each payment page script is authorised, a method to assure its integrity, and an inventory of all scripts with written justification for each one. Requirement 11.6.1 asks for change and tamper detection on payment pages, including the security headers the browser receives. Merchants on SAQ A are not exempt from the thinking, only from the specific controls: they must attest that the site is not susceptible to script attacks affecting the e-commerce system.
In practice, the inventory is the hard part. Teams that attempt 6.4.3 routinely find scripts somebody added for a campaign that ended two years ago, loaded through a tool that is still billing.
A detail worth knowing before you file the evidence: the integrity attribute on a cross-origin script does nothing without crossorigin. Without it the browser blocks the script rather than verifying it, and you find out when checkout breaks rather than when you configure it.
Tag managers are a deploy channel outside change control
A tag manager exists so marketing does not need engineering to ship a pixel. That is a reasonable goal and it is also the structural problem. The container has its own approval workflow, its own version history and its own audit trail, all of it inside a vendor dashboard that your security team probably cannot query.
Three things follow. One approved tag can pull a chain of fourth and fifth parties that never appear on the approval screen. Container changes are not linked to your release markers, so when field data degrades there is no timestamp to correlate against. And nobody owns the container as a whole, only individual tags, so the aggregate cost of the container is nobody's job.
A control loop that holds up
Inventory with owners, not a spreadsheet
Render the page in CI with a headless browser, capture every script URL, its hash, its vendor and the template it appears on, and store the result as a file in the repository. Diff it on every build. A spreadsheet is stale the day it is saved; a committed JSON file produces a reviewable change every time a script appears, changes hash or disappears.
Load policy by class
Payment SDKs are critical: pin the version, add subresource integrity, preconnect to the origin. Measurement belongs after the page is interactive or during idle time. Marketing and chat widgets should load on consent or on first interaction, not on arrival. Anything that cannot justify its cost gets blocked until someone writes down why it should run.
CSP as a ledger
Start with Content-Security-Policy-Report-Only and a report-to endpoint, collect violations for two to four weeks, then enforce on everything except the payment flow, which moves second. Nonces or hashes for first-party bundles, an explicit host allowlist for vendors, and the report stream feeding the same pipeline that holds the inventory. The policy becomes a written record of what is allowed to execute, which is most of what an auditor asks for anyway.
Field data gates
Pull p75 by page template from the Chrome UX Report rather than sitewide averages, because one slow template disappears inside a healthy site average. Alert when a template regresses more than ten percent, and stamp container changes into the same timeline so a regression has a suspect.
Contract terms
Ask vendors for versioned URLs and advance notice of breaking changes. Silent updates are a default, not a law of nature, and the ones who refuse to pin are telling you something useful about how they treat production.
Five numbers worth tracking
- p75 INP by page template, not sitewide
- script count and transferred bytes grouped by vendor
- TBT on a throttled mobile profile, run against the rendered page
- new hosts and violation volume from CSP reports
- checkout completion rate and payment form errors
That is the whole dashboard. It fits on one screen, and it answers the only question that matters after a release: did anything get slower for the people paying.
A thirty-day starting sequence
Week one, inventory. Render the top templates, list every script, and name an owner for each vendor. Expect the list to be longer than the tag manager suggests.
Week two, baseline. Turn on report-only CSP, wire the reports into logging, and record p75 INP per template so later changes have something to be measured against.
Week three, move the cheap wins. Push marketing tags behind consent and idle callbacks, pin the payment SDK with integrity and crossorigin, and drop anything nobody can justify.
Week four, enforce. Switch CSP to blocking on non-payment pages, add the inventory diff to CI, and set the alert threshold. The payment flow follows once the reports are quiet.
None of this requires a replatform. It requires applying to the browser the same discipline most teams already apply to the server: a list of what runs, a reason for each entry, and a way to notice when the list changes without permission.
The browser behaves like a production environment whether or not it is managed like one. Until the scripts inside it have owners, versions and an alert when they change, they will keep shipping without you.