Generic web performance advice treats your site like you own the server, the CDN, and every script tag. On Shopify you don’t — you’re building inside a platform box. That’s not a complaint; it’s a constraint. Once you respect the constraint, the work gets simpler: a handful of changes move real user metrics; the rest is theatre.
The honest answer is: most Shopify speed pain I see comes from images and apps. Fix those first. If you’re micro-optimising Liquid while your hero is a 3MB PNG and a reviews app is firing half the internet on scroll, you’re polishing the wrong surface.
Why generic advice misses on Shopify
You can’t SSH in and tune nginx. You can’t pick your own edge cache rules. You can’t move Shopify’s core scripts to “your server” because there isn’t one in the traditional sense. What you can control is theme payload, image delivery, how many third parties you invite to the party, and how disciplined your Liquid is on high-traffic templates.
Here’s the thing: that’s enough. I’ve taken stores from “embarrassing” to “fine” without touching anything that sounds impressive at a conference — just weight and scripts.
Core Web Vitals — what they mean in plain language
LCP (Largest Contentful Paint) — when the main visible content finishes rendering. On Shopify that’s often a hero image or a big product shot. Bad LCP is usually image sizing and format, plus render-blocking junk.
INP (Interaction to Next Paint) — how snappy the page feels when someone taps or clicks. Heavy JS from apps and sloppy event handlers show up here — not your font choice.
CLS (Cumulative Layout Shift) — stuff jumping around as things load. Often fonts, lazy images without dimensions, or banners injecting height late.
| Metric | Good target (rule of thumb) | Poor (you’ll feel it) |
|---|---|---|
| LCP | Under ~2.5s on real mobile | 4s+ — hero and third parties fighting |
| INP | Under ~200ms | 500ms+ — JS main thread busy |
| CLS | Under ~0.1 | 0.25+ — layout chaos on load |
CLS is the one that makes people swear at their phone — consent banners, late-loading review stars, variant pickers that reflow when JavaScript arrives. Fix it by reserving space: width and height on images, min-height shells for dynamic slots, and font loading that doesn’t swap metrics mid-render. Third-party marketing widgets are repeat offenders; sometimes the fix is policy (move the widget, delay until interaction, or delete it) rather than another script “optimiser”.
The 80/20 on Shopify — do these first
- Fix the biggest images — dimensions, compression, modern formats, and lazy loading that isn’t naive.
- Cut or defer third-party scripts — start with apps you don’t need (see the app audit guide).
- Remove theme cruft — old sections, duplicate CSS, abandoned snippets.
- Then — and only then — tighten Liquid on collection templates if analytics shows those pages matter.
Images — where the megabytes hide
Most stores I look at still serve heroes and collection thumbs that are larger in bytes than they are in visual detail. Use responsive images with a proper srcset, serve WebP/AVIF where the theme allows, and never trust “upload the 4000px camera file and let CSS shrink it”. CSS does not shrink download size — it only shrinks display size. The phone still downloads the monster.
Lazy loading belongs on below-the-fold content — not on the hero that defines LCP. That’s a common foot-gun: “we lazy loaded everything” and accidentally told the browser to delay the one image that mattered most.
Art direction matters too. A full-bleed hero on mobile doesn’t need the same pixel mass as a desktop cinema display — but many themes reuse one giant asset everywhere. If your theme supports separate mobile/desktop images, use them. If it doesn’t, that’s a reasonable custom tweak on a high-traffic store because it hits LCP directly.
Alt text and SEO matter for accessibility and search — but don’t confuse metadata work with bytes on the wire. I’ve seen owners spend an afternoon “optimising SEO” while the image pipeline stayed untouched. Fix the weight first; then argue about keywords.
Apps — the hidden multi-second tax
Every app is a business relationship and usually a script bundle. Stack twelve of them and you’ve built a committee meeting on the main thread. This ties directly to the Liquid vs apps decision: sometimes the fastest store is the one with fewer logos in admin, not the one with more “optimisation” toggles.
Theme bloat — when the real fix is a cleanup pass
Legacy themes accumulate. Sections nobody uses, CSS for a promo that ended in 2021, snippets copied from four tutorials. Sometimes the fastest win is an audit with a ruthless checklist: delete dead settings, merge duplicate styles, remove app hooks that aren’t wired to anything anymore. It’s not glamorous — it’s effective.
If you’re about to replatform or retheme, don’t cargo-cult the old cruft forward. Treat a migration as a chance to drop weight — the same way I’d rather move house without every broken appliance from the garage.
Liquid-side wins — when they’re worth your time
After images and apps, I look at collection templates and filters — places where an N+1 pattern of product metafield access turns into hundreds of extra work units per page. You’re not “tuning Shopify”; you’re stopping the theme from asking the same question fifty times.
Example pattern: if you’re repeatedly calling expensive filters inside loops, sometimes the fix is to compute once, assign to a variable, reuse — boring old Liquid hygiene. Another pattern: be careful with `all_products[handle]` in hot paths; it’s convenient and sometimes costly.
{%- comment -%} Fetch once per section, not per tile in the grid {%- endcomment -%}
{%- assign hero_collection = collections[section.settings.collection] -%}
{%- for product in hero_collection.products limit: 8 -%}
{%- comment -%} Use product fields you already have on the object {%- endcomment -%}
{% render 'product-card', product: product %}
{%- endfor -%}
That’s illustrative — your theme’s structure varies — but the mindset doesn’t: don’t repeat work inside loops that run dozens of times per page load.
{%- comment -%} Avoid hidden all_products lookups in a tight loop {%- endcomment -%}
{%- for block in section.blocks -%}
{%- assign featured = all_products[block.settings.product] -%}
{%- comment -%} If you need this, consider caching strategy or section settings {%- endcomment -%}
{%- endfor -%}
What’s usually not worth obsessing over on Shopify
Micro-tweaks that move a lab score two points but don’t change LCP/INP/CLS in the field. Server-side caching fantasies — you don’t control that stack. “CDN tuning” — Shopify’s CDN is what it is. Chasing perfect Lighthouse on a store that depends on five marketing pixels — pick your battles.
Tools I actually use
PageSpeed Insights — lab + field when available; sanity-check mobile. Web Vitals extension — quick gut check while clicking around. Your own phone, not just Wi‑Fi at the office. Shopify admin speed report — glance, don’t worship.
I also keep an eye on the Network waterfall for third parties: if you see long chains of redirects or scripts that block for seconds, that’s a conversation about vendors, not about “Shopify being slow”. The platform gets blamed for sins committed by tag managers.
Record a short screen capture of a cold load on mobile data and watch it with the sound off. If you get impatient before the hero settles, assume customers are already leaving — they’re less patient than you are.
If you want a second opinion after your own pass, bring the URLs and your Network waterfall to a call — I’m happier looking at evidence than arguing about vibes.
One blunt rule: if you haven’t measured after a change, you didn’t finish the change — you just moved pixels around.
A one-day speed audit you can run without me
- Pick three URLs: home, your bestselling collection, your bestselling product. Test each on mobile PageSpeed.
- Open DevTools → Network, throttle to Fast 4G, reload — sort by size. Fix the top three image offenders.
- List apps; disable the heaviest non-critical one on a duplicate theme preview if you can — measure again.
- Click add-to-cart and checkout start — watch for long tasks in Performance panel. If apps spike, re-run the app audit.
- Ship the image fixes first, then reassess before touching Liquid architecture.
Shipping and checkout performance overlap more than people admit — slow carrier endpoints show up as “checkout feels sticky”. If you’re debugging that, cross-read carrier-calculated vs standard rates. And if you’re about to replatform anyway, bundle speed work into a migration so you’re not optimising a theme you’re about to retire.
When the problem is bigger than a weekend audit, that’s what fixes and custom work is for — I’m not precious about whether you DIY first; I just care that you work on the right layer.
If you’re coming from a slow WordPress/Woo stack, speed work on Shopify is only half the migration story — the other half is leaving behind shared hosting and plugin soup. WordPress vs Shopify for NZ small businesses is my honest write-up from having lived both sides.