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 $95/hour is ~$30K. ROI: 35×. 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.