Retail & E-commerce// diagnostic

The storefront got slower and nobody on your team shipped any code

In short

When a storefront slows with no deployment behind it, the change came from a script installed without a release — an app, a tag, or a vendor bundle that updates itself behind a stable URL. Capture the page twice on one throttled profile, once normally and once with third-party origins blocked, and diff the request waterfall rather than the score. The diff names a script; the score names a symptom.

Key takeaways

  • Diff request waterfalls, not scores. A score says the page is slower; a waterfall says which host made it slower.
  • Bisect by origin, one at a time, on a fixed throttled profile with 5 runs and a median. Guessing from a vendor list wastes days.
  • A stable script URL is not a stable script. Vendors ship new bytes behind the same path, so record transferred size weekly.
  • Some scripts genuinely cannot be deferred. Consent tooling, anti-flicker snippets and anything rendering content are honest exceptions.
  • Governance is the durable fix: every third-party script needs a named owner, a reason and a removal condition before it is installed.

Nobody deployed anything and the product page is 600 milliseconds slower than it was a month ago. That combination is not mysterious — it is the signature of a change made outside the release process. Someone installed an app, published a tag, or a vendor pushed a new bundle behind a URL that never changed. All 3 happen without a pull request and none of them appears in your deployment log.

The mistake that follows is measuring the wrong thing. A performance score tells you the page got slower, which you already knew. What you need is the name of the script, and that comes from comparing 2 request waterfalls rather than 2 numbers.

Diff the waterfall, not the score

The capture has to be controlled, because page performance is noisy enough to hide the effect you are hunting. Fix the profile, then change exactly one thing.

  1. Pick one product page and one throttled profile — a mid-range mobile CPU multiplier and a constrained network — and use it for every run. A laptop on office wifi cannot see this problem at all.
  2. Run the page 5 times and take the median. A single run will move by more than the regression you are chasing.
  3. Repeat with every third-party origin blocked at the request level. Most browser developer tools can block by domain pattern; a proxy works too.
  4. Diff the 2 waterfalls by request, not by score: which hosts appear, how many requests each made, transferred bytes per host, and where each one sits relative to the largest element.
  5. Then unblock origins one at a time and re-run. Bisecting by origin is slower than reading a vendor list and it is the only method that produces a name you can act on.
  6. Record main-thread time per script as well as bytes. A 30 KB script that parses JSON on the main thread can cost more than a 200 KB image.

Attribute to a script, not to a category

'Reviews are slow' is not a finding. 'The review widget's loader pulls a second bundle from a different host, and both run before the hero image is requested' is a finding, because it can be fixed 3 ways and only 1 of them involves removing reviews from the page.

  • Count hosts, not vendors. One app commonly loads from 2 or 3 domains, and blocking only the obvious one produces a diff that says the app is innocent.
  • Watch for chained loads. A loader that fetches a config file that fetches a bundle serialises 3 round trips before any work starts, and each hop is a full network latency on a mobile connection.
  • Note whether the script is in the document head and synchronous. That single property usually explains more than the file itself.
  • Check whether it writes to the page. A script that injects markup or edits prices is doing rendering work, which is why deferring it changes what the shopper sees rather than only when they see it.

Four ways a storefront slows with nobody shipping

What happenedHow to confirm itWho can undo it
A new app was installed from a platform marketplaceCompare the host list against last month's capture. A new host is unambiguousWhoever installed it, usually without asking
A tag rule was scoped to all pages instead of oneThe tag fires on pages where its feature is not used. Check the container's trigger conditionsThe tag manager owner, immediately
A vendor shipped new bytes behind the same URLTransferred size for that path changed while the path did not. Only a recorded history shows thisNobody on your side. You can only cap or remove it
A personalisation or anti-flicker snippet started blocking paintFirst paint is delayed by close to the snippet's own timeout when the vendor is slowWhoever owns the experimentation tool
The change that was not a deployment

The third row is the one that survives every audit, because there is nothing new to notice. The script URL is identical to the one that was reviewed 6 months ago and the bytes behind it have doubled. The only defence is a recorded history: log transferred size and a content hash per third-party path weekly, and alert on change. That is a small standing job rather than an investigation, which is exactly the kind of thing that belongs in internal tools and operations software instead of a quarterly panic.

A stable URL is not a stable script. The path was reviewed six months ago; the bytes behind it were not.

The governance rule: what may load before the product image

Most of the argument disappears once a storefront has a written answer to one question: what is allowed to execute before the largest element on the page has been requested? Without that rule, every vendor's installation guide says 'paste this in the head', and every one of them is obeyed.

  1. Nothing third-party goes in the critical path by default. Every tag is deferred or asynchronous unless it appears on a written exception list with a named owner.
  2. The exception list is short and each entry states what breaks if it is deferred. If nobody can say what breaks, it is not an exception.
  3. Set a ceiling on third-party JavaScript for the page and enforce it in the build or in monitoring, not in a document. A page-level budget makes the trade explicit — this is the allocation exercise in the performance budget a product page is built against.
  4. Every script gets an owner, a business reason and a removal condition at install time. Most storefronts carry tags for tools nobody has used in a year, and the removal condition is the only thing that ever gets them off the page.
  5. Review the host list monthly against the previous capture, and treat a new host as a change requiring the same approval as a code change. Doing that during peak trading needs the graded release approach in shipping storefront changes during peak trading.

One measurement caveat is worth stating plainly: the published thresholds for the field metrics people quote — largest contentful paint, interaction latency — are periodically revised, and the definitions themselves have changed more than once. Check the current definition against the source rather than against a number remembered from an article, this one included. What does not change is the ordering: a script that blocks paint is worse than one that does not, at any threshold.

The scripts that genuinely cannot be deferred

An honest audit ends with a short list of things that have to run early, and pretending otherwise produces a fast page with broken features. Four categories recur.

  • Consent tooling. It has to run before whatever it gates, so it is early by construction. What you can control is its size and whether it fetches its configuration from a second host.
  • Anti-flicker snippets for experimentation. They hide content until the variant is known, which is the entire point. Cap the timeout aggressively and accept that some shoppers see the default.
  • Anything that renders content above the fold. A widget that draws the availability badge or a price is a rendering dependency, not a decoration — and deferring it makes the page show a value that is briefly wrong, which is the same class of problem as the in-stock badge that is a cached lie.
  • Market and currency resolution. A geo script that redirects or rewrites prices has to settle before the shopper reads the page, and doing it in the browser creates the routing conflicts traced in shoppers landing on the wrong country's storefront.

The last 2 have a better answer than tuning: move the work to the server. Prices that must differ per account can be rendered as an authenticated fragment rather than patched in by a script — the split described in rendering contract pricing without leaking it — and market resolution belongs in routing rather than in JavaScript. Both changes remove a blocking script instead of scheduling it better.

There is a second reason to prefer server-rendered values. Clients that do not execute your scripts — crawlers, feed readers, shopping agents — see only what the server sent, so a price or an availability injected in the browser does not exist for them. That is one route to a store quoting 2 different numbers on 2 different surfaces, alongside the agent-side version examined in the agent's total and your total disagreeing.

None of this is a one-off clean-up. Scripts accumulate because installing one is easy and removing one requires knowing why it is there, which is a storefront architecture governance problem more than a technical one, and it sits alongside the rest of the decisions in retail and e-commerce software.

Frequently asked questions

Short answers to the follow-ups this page tends to raise.

How do we prove which app caused the slowdown?

Bisect by origin on a fixed profile. Capture the page with all third-party hosts blocked, then unblock them one at a time, taking a median of 5 runs at each step. The step where the metric moves names the host. It is slower than reading a vendor comparison, and it is the only method that produces evidence you can put in front of the person who installed the app.

Can we just defer every third-party script?

No, and trying it is how storefronts ship pages that are fast and wrong. Consent tooling, anti-flicker snippets and anything that renders content above the fold have to run early by construction. The workable rule is the reverse: defer everything by default, and keep a short written exception list where each entry says what breaks if it is deferred.

Is a tag manager the cause or the cure?

Both, which is why it needs an owner. A container makes it trivial to add a tag without a release, so it is usually where the accumulation happened. It is also the single place you can audit and remove them, set trigger conditions properly, and stop a tag meant for one page from firing on all of them. The tool is neutral; the governance around it is not.

How often should the script inventory be reviewed?

Monthly for the host list, and continuously for bytes. A monthly diff of hosts catches new installs, while an automated weekly record of transferred size per third-party path catches the vendor that changed the bundle behind an unchanged URL. The second is the one manual review never finds, because there is nothing new on the page to notice.

  • performance
  • third-party scripts
  • storefront architecture
  • governance
// shipped work

The work behind this page

Builds from our portfolio that this page draws on.

Read next

Working on something in this space?

Tell us where you are in a sentence or two. We'll tell you honestly whether we're the right team, and what a sensible first slice of the work looks like.

Start the conversation