1. The economics: why Shopify speed = revenue
Slow Shopify stores leak revenue at every stage. The relationship is empirical, not theoretical:
- Google rankings: Shopify stores with LCP > 2.5s on mobile rank an average of 3.2 positions lower than peers with LCP < 1.6s (Sistrix, Q1 2026).
- Conversion rate: every 1s LCP reduction correlates with a 7–12% lift in CR (Deloitte, 'Milliseconds Make Millions', 2024 update).
- AOV: faster stores have 8–14% higher AOV (Akamai 2024 e-commerce report). Hypothesis: customers browse more product pages when each loads quickly.
- Bounce rate: a store moving from 4s LCP to 1.5s sees bounce rate drop from ~58% to ~31% (CrUX dataset analysis, 100+ stores).
Going from 3.4s LCP to 1.6s typically lifts CR by ~12% and AOV by ~8%. Compounded: 1.12 × 1.08 = 1.21, or +21% revenue. On $5M GMV, that's $1.05M of new revenue. Six weeks of engineering at $49/hour is ~$15K. ROI: 70×. This is why we lead with this work.
2. Week 1 — audit + baseline
Before any code changes, capture the baseline. Without a baseline, you can't measure impact. Without measurement, the project is religion.
- PageSpeed Insights for the homepage, top 5 collection pages, top 10 product pages, and the cart. Capture both lab (Lighthouse) and field (CrUX) scores.
- WebPageTest run from a target geography on a 4G throttled connection. The waterfall view shows what's blocking — that's the engineering target list.
- Shopify Theme Inspector (the dev tools panel) for Liquid render times.
- App audit: list every installed app, the JS/CSS each one injects, and on which pages. Many stores have 25–40 apps; about half are on every page.
- Image audit: total bytes, formats, dimensions, lazy-loaded vs eager.
// Lists all third-party scripts loaded by the storefront
const scripts = Array.from(document.scripts)
.filter(s => s.src && !s.src.includes(location.host))
.map(s => ({
domain: new URL(s.src).hostname,
size: 'check Network tab',
blocking: !s.async && !s.defer,
}));
console.table(scripts);
// Count scripts by domain
const byDomain = scripts.reduce((acc, s) => {
acc[s.domain] = (acc[s.domain] || 0) + 1;
return acc;
}, {});
console.table(byDomain);
// Output: judkoglobal.com: 4, klaviyo.com: 2, hotjar.com: 3, ...
// Each domain is a third-party doing work on the main thread.3. Week 2 — theme architecture
Shopify Online Store 2.0 themes (Dawn-derived) are dramatically faster than 1.0 themes by default — but they only stay fast if the architecture is preserved. Common architectural mistakes we see:
- Sections that load on every page even though they're only used on 2 pages. Theme settings let you scope sections; use them.
- Apps that inject JavaScript into theme.liquid (loads on every page) vs into specific section files (only the relevant pages).
- Custom theme code that imports the entire theme's JS bundle to add a single feature.
- Render-blocking external CSS (e.g., Google Fonts loaded before the critical render path).
{% comment %} Custom Featured Reviews section — only on PDP {% endcomment %}
{% if template == 'product' %}
<section class="custom-reviews" data-product-id="{{ product.id }}">
<h2>Customer Reviews</h2>
<div id="reviews-container">
<div class="skeleton" aria-label="Loading reviews">…</div>
</div>
</section>
{% comment %} Defer the JS until after the main content loads {% endcomment %}
<script
src="{{ 'reviews.js' | asset_url }}"
defer
data-cfasync="false"
></script>
{% endif %}
{% schema %}
{
"name": "Featured Reviews",
"tag": "section",
"presets": [{ "name": "Featured Reviews" }],
"templates": ["product"]
}
{% endschema %}4. Week 3 — image strategy
Images are the largest single contributor to LCP on Shopify product pages. Shopify's CDN supports modern formats and responsive sizing — most stores aren't using either correctly.
- Use the image_url filter with width parameter for responsive srcset. NOT img_url (deprecated in OS 2.0).
- Specify width and height attributes on every image. Prevents CLS.
- First product image (LCP candidate): add fetchpriority='high' and loading='eager'. Other product gallery images: loading='lazy'.
- Lazy-load below-the-fold images. Default in Dawn but easy to break with custom sections.
- Use AVIF format where supported. Shopify's CDN auto-converts to WebP/AVIF when you use the image_url filter — but only if the source is high-resolution.
{% liquid
assign hero = product.featured_image
assign sizes = '100vw'
%}
<div class="product-hero" style="aspect-ratio: 1 / 1;">
<img
srcset="
{{ hero | image_url: width: 400 }} 400w,
{{ hero | image_url: width: 800 }} 800w,
{{ hero | image_url: width: 1200 }} 1200w,
{{ hero | image_url: width: 1600 }} 1600w
"
sizes="{{ sizes }}"
src="{{ hero | image_url: width: 800 }}"
alt="{{ hero.alt | escape }}"
width="{{ hero.width }}"
height="{{ hero.height }}"
fetchpriority="high"
loading="eager"
/>
</div>Replacing a 480KB JPEG hero with a srcset-driven AVIF hero (browser picks 280KB version on mobile) typically drops LCP by 1.0–1.6s on 4G. The single most-impactful image change.
5. Week 4 — third-party app surgery
Most Shopify stores have 20–40 apps. Each app injects scripts on the storefront. Each script blocks the main thread or competes for network bandwidth. By week 4 we have full data — now we cut.
- Audit each app's value vs cost. We've removed apps that injected 200KB of JS to provide a feature the merchant could have done in 12 lines of Liquid.
- Defer all non-critical apps. Klaviyo's site tracking can be deferred. Hotjar can be deferred. Reviews widgets can be deferred (or rendered server-side via Shopify's built-in product reviews when possible).
- Replace heavy apps with first-party features where possible. Shopify has built-in reviews, abandoned cart emails, and basic customer accounts — many merchants have apps that duplicate these.
- Use the 'Web Pixels' API for analytics where supported. Web Pixels run in a sandboxed worker, isolated from the main thread.
| App category | Typical JS weight | Defer-able? | Replacement option |
|---|---|---|---|
| Reviews (Yotpo, Judge.me) | 120–280KB | Yes (lazy on PDP scroll) | Shopify product reviews |
| Email capture (Privy) | 80–150KB | Yes (defer until idle) | Shopify forms + Klaviyo direct |
| Analytics (multiple) | varies | Mostly | Web Pixels API for GA4 |
| Live chat (Tidio, Gorgias) | 100–250KB | Yes (load on click) | Same — but lazy-load |
| Upsell / cart drawer | 50–180KB | Partial | Custom Liquid + minimal JS |
6. Week 5 — Liquid optimization
Liquid is server-side; you can't optimize render time on the client. But you can reduce what Liquid renders to begin with. Three patterns:
- Pagination on collection pages. Default Dawn paginates at 24. We move to 16 on mobile, 24 on desktop, with infinite scroll deferred until user scrolls. Initial HTML weight drops 30–40%.
- Lazy-rendered sections. Use the new 'block' API to render sections after first paint. Shopify supports this in OS 2.0 with proper section schemas.
- Trimmed product JSON. Many themes dump the full product object into the page (variants, all images, all metafields). Trim to only what the customer-facing JS actually needs.
- Reduce conditional logic. Deeply nested {% if %} chains slow Liquid render. We've seen Liquid render times drop from 320ms to 84ms by simplifying conditionals on heavily-used sections.
7. Week 6 — validation + monitoring
Performance work that isn't monitored regresses. Apps update, new sections get added, marketing installs a new tracking pixel. Without monitoring, you'll be back to 3.4s LCP in a quarter.
- Set up Vercel Speed Insights or Cloudflare Web Analytics for real-user monitoring. Per-page LCP/INP/CLS, segmented by device.
- Add Lighthouse CI to your theme deploy pipeline. Block deploys that regress LCP > 0.3s on the homepage or top product page.
- Monitor third-party JS weight. Each new app install gets reviewed; deploys that add > 50KB of third-party JS without justification get blocked.
- Quarterly audits. CWV regresses by 6–14% on average per quarter without active maintenance. Schedule a 4-hour audit + fix cycle every quarter.
DTC home goods, ~$5M GMV. Started at 3.4s LCP. After 6 weeks: 1.4s. CR up 14%. AOV up 9%. Q1 revenue: +21%. Six months later, with monitoring + quarterly audits: still at 1.5s. Without monitoring, our regression model predicts they'd have been back to 2.6s by month six.
8. Common Shopify performance traps
- Installing a 'speed' app. The irony is that most speed apps add 80–150KB of JavaScript to do work the platform already does (lazy-load images, defer scripts). They typically make CWV worse, not better.
- Optimizing only for Google's ranking — not for users. Lighthouse score 95 with real-user LCP 4.5s is common. The CrUX field data is what matters.
- Replacing the theme without a migration plan. Yes, faster theme = faster site, but URL structure changes break SEO equity unless redirects are mapped 1:1.
- Going headless (Hydrogen) for the wrong reasons. Headless solves specific problems (omnichannel content, deep customization). It does NOT inherently make Shopify faster — and operational cost goes up significantly.
- Ignoring JavaScript bundle from custom apps your team built. We've seen $10K+ of custom-app development sit unused on every page because nobody reviewed it after the original feature shipped.
Want this handled by senior operators instead of read about? Our Software Management practice turns the ideas above into shipped work — or explore everything we do below.