All articles
E-commerceMay 30, 2026 7 min read

The Hidden Cost of Shopify Apps: How We Audited a Stack and Recovered 900ms on Mobile

A Shopify merchant came to us with a 4.2s LCP and 34 installed apps. Here's how we ran the audit, which apps actually mattered, and what we ripped out to recover almost a second on mobile.

The Hidden Cost of Shopify Apps: How We Audited a Stack and Recovered 900ms on Mobile

A merchant came to us last quarter with a familiar story: conversion was sliding on mobile, Lighthouse was screaming, and the theme dev kept blaming "the apps". They had 34 installed. We didn't refactor a single Liquid template in week one. We just audited the app stack and pulled 900ms off mobile LCP.

This is the playbook we used. It works for most Shopify stores running on a modern Dawn-based theme, and it does not require going headless.

Why the app stack is usually the first place to look

Shopify themes in 2026 are mostly fine out of the box. Dawn, Sense, and the well-built Theme Store entries ship with reasonable critical CSS, deferred scripts, and lazy-loaded media. What ruins them is what gets layered on top.

Every app you install typically does one or more of:

  • Injects a <script> tag via ScriptTag API or theme app extension
  • Adds a Liquid block that renders synchronously
  • Loads a third-party SDK (Klaviyo, Yotpo, Gorgias, Rebuy, Loox, etc.)
  • Registers a web pixel that fires on every page
  • Adds a CSS bundle that fights your theme's cascade

Individually, none of these are catastrophic. Stacked, they are. We have seen stores where the app-injected JavaScript was 4x larger than the theme's own bundle.

The 80/20 rule we use

In our experience, roughly 20% of installed apps generate 80% of the third-party performance cost. The job of an audit is to find that 20% without guessing.

Step 1: Build the inventory

Before touching anything, get a complete picture. The Shopify admin app list lies — it doesn't tell you which apps actually inject runtime code versus which ones just sit in the background syncing data.

Here's the quick triage we run:

# From the storefront, grab all third-party script origins
curl -s https://store.example.com/products/bestseller \
  | grep -oE 'src="https?://[^"]+"' \
  | sort -u

Then we open the page in Chrome with the Network panel filtered to JS, sorted by transfer size. We export the HAR and tag each request:

  • Theme — first-party, your bundle
  • Shopify core — checkout, analytics, web pixels manager
  • App runtime — injected by an installed app
  • Marketing pixel — Meta, TikTok, Google, Pinterest

For the merchant in question, the breakdown looked like this:

CategoryRequestsTransfer (gzipped)
Theme6~85 KB
Shopify core4~60 KB
App runtime19~520 KB
Marketing pixels7~180 KB

The app runtime was 6x the theme. That's where the work is.

Step 2: Map each app to a business owner and a metric

This is the part most engineering audits skip, and it's why they get overruled in the meeting. For every app, you need two answers:

  1. Who owns this? (marketing, ops, support, CX, finance)
  2. What metric does it move? (AOV, repeat rate, ticket deflection, review count)

If nobody can answer either question, the app is already dead. You just haven't uninstalled it yet.

On this audit we found:

  • Two review apps installed (one from a previous agency, one current). The old one was still injecting a 90KB widget.
  • A wishlist app with 0.3% engagement that loaded 140KB on every PDP.
  • An upsell app duplicating logic the theme already handled in the cart drawer.
  • A currency converter that the merchant had replaced with Shopify Markets six months earlier but never uninstalled.

None of these required A/B testing to kill. They needed a Slack message.

The "prove it stays" rule

For anything ambiguous, we flip the burden of proof. The app does not get to stay because someone might use it. The owner has to point to a dashboard, a revenue number, or a workflow that breaks without it. If they can't, it goes into a two-week removal trial.

Step 3: Measure the real cost per app

For apps that survived step 2, we measured actual runtime cost. The cleanest way on Shopify is to use the theme editor's app embed toggles plus URL parameters to disable selectively, then run paired Lighthouse runs.

// Quick browser console snippet to list every script and its size
performance.getEntriesByType('resource')
  .filter(r => r.initiatorType === 'script')
  .map(r => ({
    url: new URL(r.name).hostname + new URL(r.name).pathname,
    transferKB: Math.round(r.transferSize / 1024),
    durationMs: Math.round(r.duration)
  }))
  .sort((a, b) => b.transferKB - a.transferKB);

We run this on PDP, collection, and cart. Three pages, three runs each, median values. The output is a per-app cost sheet that looks like:

  • Reviews app A: 92 KB, 180ms parse on mid-tier Android
  • Upsell app: 140 KB, 240ms
  • Live chat: 210 KB lazy-loaded after 3s (acceptable)
  • Analytics app: 65 KB, fires synchronously in <head> (not acceptable)

Now the conversation with the merchant is concrete. "This app costs you 240ms on mobile PDPs. It generates $X in attributed revenue. Is the trade worth it?"

Step 4: Fix what you keep

For apps that survived the cull, there's usually still optimization to do.

Move app embeds out of the critical path

Shopify's theme app extensions support app embed blocks. Many merchants enable them globally when they only need them on specific templates. In the theme editor, check every app embed and disable it on templates where it isn't used. A live chat app does not need to load on the checkout success page.

Defer what can be deferred

If an app injects a script tag without defer or async, you can often intercept it. We use a small theme snippet that rewrites known third-party tags. It's brittle — vendor updates can break it — so document it heavily:

{%- comment -%} snippets/defer-thirdparty.liquid {%- endcomment -%}
<script>
  // Defer non-critical vendor scripts until after first interaction or 3s idle
  (function() {
    const defer = ['vendor-a.com', 'vendor-b.io'];
    const observer = new MutationObserver(muts => {
      muts.forEach(m => m.addedNodes.forEach(node => {
        if (node.tagName === 'SCRIPT' && node.src) {
          if (defer.some(d => node.src.includes(d))) {
            node.setAttribute('data-deferred', node.src);
            node.removeAttribute('src');
          }
        }
      }));
    });
    observer.observe(document.documentElement, { childList: true, subtree: true });

    const load = () => {
      observer.disconnect();
      document.querySelectorAll('script[data-deferred]').forEach(s => {
        s.src = s.getAttribute('data-deferred');
      });
    };
    ['click', 'scroll', 'keydown'].forEach(e =>
      window.addEventListener(e, load, { once: true, passive: true })
    );
    setTimeout(load, 4000);
  })();
</script>

Use this carefully. Anything that needs to fire before consent (CMP, certain pixels) or that tracks early page events will misbehave. Test thoroughly.

Consolidate pixels through Shopify's Customer Events

If you're still loading Meta, TikTok, and Google pixels via three separate apps, you're paying triple. Move them into Customer Events (web pixels) where Shopify sandboxes them in a worker. This alone can shave 100–200ms off main-thread blocking on a busy store.

Step 5: Lock the gate

The audit only sticks if you stop the bleeding. We add three things to the merchant's ops:

  1. App install policy — no new app gets installed without a 30-day review tied to a metric.
  2. Quarterly re-audit — same HAR diffing process, calendar invite, owner assigned.
  3. Performance budget in the theme repo — a simple CI check that fails the build if total JS transferred on a sample PDP exceeds a threshold.

We document this in the merchant's internal wiki, not just our handoff doc. If it lives only with the agency, it dies the day the contract ends.

What the numbers looked like

After the cull and the deferral work, on the same mid-tier Android profile and throttled 4G:

  • Mobile LCP: 4.2s → 3.3s
  • Total blocking time: ~780ms → ~310ms
  • Mobile transferred JS on PDP: ~760KB → ~340KB
  • Apps installed: 34 → 19

Conversion rate moved positively in the following four weeks, but we're not going to attribute that cleanly to the audit — there were three other things changing at the same time, including a checkout extension rebuild. Anyone who tells you a single performance change drove a clean conversion lift is selling you something.

Where we'd start

If you inherit a slow Shopify store tomorrow, do not refactor the theme first. Run the inventory in step 1, get the per-app cost sheet in step 3, and have the "prove it stays" conversation. You will almost certainly find a free 200–500ms before you write a line of Liquid.

If the audit ends and you're still over budget on mobile, that's when you start looking at theme rendering, image strategy, or — only at the end — whether headless is actually worth the tax. We've written about that tradeoff before, and the answer is still: usually not. Fix the app stack first.

If you want a second pair of eyes on yours, that's the kind of work we do under e-commerce engineering.

#Shopify#Performance#CRO#E-commerce

Want a team like ours?

72Technologies builds production software for the kind of teams who actually read this blog.

Start a project