Why your Shopify store is slow — and the five fixes that move the needle
Most store-speed advice is a checklist of forty things that each save four milliseconds. Here are the five that actually change what a customer feels, ranked by how much they matter on a real store.
I get asked to "make the store faster" more than almost anything else, and nearly every time the merchant arrives with a Lighthouse score and a sense of dread. The score is rarely the problem. The problem is that a handful of decisions — usually made by people who never opened the codebase — are quietly costing the store its load time. Fix those, and the score follows. Chase the score directly, and you'll spend a week shaving milliseconds nobody will ever notice.
So before anything else: stop optimizing for the lab and start optimizing for the field.
Measure the thing customers actually feel
Lighthouse runs a simulated load on a simulated device. It's useful for catching regressions, but it is not how your customers experience the store. What you want is field data — real loads from real phones on real networks — and that comes down to three Core Web Vitals:
- LCP (Largest Contentful Paint) — how long until the main thing on the page shows up. On a product page that's usually the hero image. Aim for under 2.5 seconds.
- INP (Interaction to Next Paint) — how quickly the page responds when someone taps or clicks. This replaced FID, and it's stricter. Aim for under 200ms.
- CLS (Cumulative Layout Shift) — how much the page jumps around while loading. Aim for under 0.1.
Pull these from the Chrome User Experience Report or your real-user monitoring, not from a one-off Lighthouse run. Once you're looking at field numbers, the five fixes below are almost always where the time is hiding.
1. Cut the app bloat (this is usually 60% of the problem)
This is the single biggest lever on most stores, and it has nothing to do with code you wrote. Every app you install can inject its own JavaScript and CSS into every page — review widgets, upsell popups, currency switchers, "someone in Ohio just bought this" notifications. Ten apps, ten extra render-blocking bundles, most of them loading on pages where they do nothing.
Audit what's actually running. Open the network tab on a product page and sort by size. You'll usually find two or three apps responsible for the bulk of the third-party weight, and at least one app you forgot you installed. Uninstalling cleanly (not just disabling) and removing orphaned script tags is often a bigger win than any code change.
The fastest line of JavaScript is the one you deleted.
2. Fix your images properly
Images are almost always the LCP element, and they're almost always shipped wrong. Three things matter:
- Serve the right size. Use Shopify's image transformation parameters and a real
srcsetso a phone downloads a phone-sized image, not the 2400px desktop hero. - Use modern formats. Shopify will serve WebP automatically to supporting browsers when you use its CDN URLs — make sure your theme isn't bypassing that with hardcoded paths.
- Reserve the space. Always set width and height (or an aspect-ratio) so the browser holds the slot. This is the number-one cause of CLS.
And lazy-load everything below the fold — but never the LCP image. Lazy-loading your hero is a classic own-goal that delays the one paint you're trying to speed up.
<!-- Responsive, CDN-sized, space reserved, eager for the hero -->
<img
src="{{ product.featured_image | image_url: width: 800 }}"
srcset="{{ product.featured_image | image_url: width: 400 }} 400w,
{{ product.featured_image | image_url: width: 800 }} 800w,
{{ product.featured_image | image_url: width: 1200 }} 1200w"
sizes="(max-width: 750px) 100vw, 50vw"
width="1200" height="1500"
loading="eager" fetchpriority="high"
alt="{{ product.featured_image.alt | escape }}">
3. Tame third-party scripts
Analytics, chat widgets, pixels, A/B testing tools — none of them should block your page from rendering. The fixes are unglamorous but reliable: load them with defer or async, push anything non-essential to load after first interaction, and move checkout-page tracking into Shopify's Web Pixels sandbox rather than injecting it inline.
That last point isn't optional anymore. Since the move to Checkout Extensibility, the old habit of pasting tracking snippets into Additional Scripts is gone — and the sandboxed pixel approach is both the supported path and the faster one. If your store is still leaning on legacy script injection, you have a performance problem and a deprecation problem in the same place.
4. Stop blocking the first render
Render-blocking resources are CSS and JS the browser must finish before it can paint. On a typical theme the culprits are a giant global stylesheet, a web-font that loads invisibly, and a blob of theme JavaScript at the top of the page.
- Inline the critical CSS for above-the-fold content and load the rest asynchronously.
- Set
font-display: swapso text shows immediately in a fallback font instead of staying invisible. preconnectto your CDN and font hosts so the handshake happens early.- Defer theme JavaScript that isn't needed for the first paint.
5. Respect the Liquid in your loops
This is the one that's actually your code, and it's where a fast theme quietly turns slow as the catalog grows. The usual offenders are loops that do too much work per iteration and pages that render the entire collection instead of paginating.
- Paginate collections and use the Section Rendering API to load more on demand instead of dumping 200 products into the DOM.
- Hoist work out of loops — don't run an expensive filter or lookup once per product when you can do it once before the loop.
- Lazy-render heavy sections that live far down the page.
None of this shows up on a small dev store with twelve products. All of it shows up on the real one with twelve thousand.
When speed becomes an architecture question
Do these five things well and the vast majority of stores land in the green without touching the platform. A small number won't — stores with genuinely complex, app-like front ends where you're fighting the theme on every page. That's the point where headless stops being hype and starts being a real option, and it deserves its own honest conversation, which I get into over here.
The short version
- Measure field Core Web Vitals (LCP, INP, CLS), not Lighthouse scores.
- App bloat is usually the biggest single cost — audit and uninstall ruthlessly.
- Right-size images, reserve their space, and never lazy-load the hero.
- Defer third-party scripts; move checkout tracking into Web Pixels.
- Keep your Liquid loops lean and paginate large collections.
