Shopify Speed Optimization: Practical Fixes for Faster Stores

Shopify Speed Optimization: Practical Fixes for Faster Stores


Shopify handles hosting, a global CDN and image processing for you, so it’s easy to assume every Shopify store is fast. Many aren’t. A store that felt quick at launch slowly gets heavier as apps are installed, tracking tags are added, banners grow and the theme picks up years of small changes. Eventually the product page takes four or five seconds to show its main image on a phone, and some shoppers leave before it does.

The good news is that most Shopify speed problems come from a short list of causes, and most fixes are in your control: the theme, the apps and the content. This guide walks through how I audit a slow store, the fixes that make the biggest difference, and the Liquid code behind them.

What you can and can’t control on Shopify

On WordPress you can tune the server, the database and the caching layer. On Shopify you can’t, and you don’t need to: Shopify’s infrastructure is already fast and cached. Server response time is rarely the problem on a Shopify store. What you do control is everything the browser has to download and run once the HTML arrives:

  • The theme: its HTML, CSS, JavaScript and how it loads images.
  • Apps: the scripts, styles and widgets they add to your pages.
  • Third-party tags: analytics, pixels, chat, reviews, pop-ups.
  • Content: the size of your images and videos, and how many sections each page has.

That makes Shopify optimization a frontend job. If you also run WordPress sites, my WordPress Core Web Vitals checklist covers the server side that Shopify handles for you.

Measure first: the right tools

Don’t optimize by guesswork. These are the tools I use, in order:

  1. Shopify’s web performance report in the admin (Online Store → Themes, or the Analytics reports). It shows Core Web Vitals from real visitors on your store, which is what Google uses too. Real-user data takes time to accumulate, so check trends over weeks rather than days.
  2. PageSpeed Insights for the home page, a collection page and a product page. Look at the field data first, then use the lab diagnostics to see why.
  3. Chrome DevTools (Network and Performance panels) to see exactly which scripts load and which ones keep the main thread busy.
  4. Theme Check (shopify theme check in Shopify CLI), which flags performance problems in theme code such as parser-blocking scripts and oversized assets.

Test the product page on a mobile profile before anything else. It usually earns the most revenue and is usually the heaviest page in the store.

What actually slows a Shopify store

When I open a slow product page in DevTools, the same culprits show up again and again. Here’s where they live on a typical page:

A product page with six common slowdowns marked: web fonts in the header, app scripts, an oversized hero image that is usually the Largest Contentful Paint element, a third-party review widget, a chat widget and tracking pixels.Aa3 app scripts128 reviewsAdd to cartpixels1234561Web fontsSeveral families and weights delay text.2App scriptsEach app can add JS and CSS to every page.3Hero image (usually the LCP)Too large, or lazy-loaded by mistake.4Review widgetThird-party script and extra requests.5Chat and pop-upsHeavy widgets loaded before they're needed.6Tracking tagsPixels compete for the main thread.
Figure 1: The six most common slowdowns on a Shopify product page.

Notice that only one of the six (the hero image) is about content. The rest is code that somebody added to the store. That’s why an app and tag audit is almost always the first and most effective step.

Fix 1: Audit your apps

Apps are the single biggest source of extra JavaScript on most Shopify stores. Many load on every page even when their feature appears on only one, and some leave code behind in the theme after they’re uninstalled. I go through every installed app with these questions:

App audit flowchart. If an app is not still used, uninstall it and remove leftover code. If it is used but not needed on every page, disable its app embed or limit it to the pages that use it. If Shopify can do the job natively, replace it with metafields, sections or built-in features. Otherwise keep it and review it again next quarter.For each installed appIs it still used and earning its place?NoUninstall itThen remove leftover theme code.YesIs it needed on every page?NoLimit where it loadsApp block on the pages that use it.YesCan Shopify do this natively?YesReplace itMetafields, sections or built-ins.NoKeep itReview it again next quarter.
Figure 2: A simple decision process for every installed app.

Remove leftover app code

Older apps often wrote code straight into theme.liquid or added snippets, and uninstalling the app doesn’t always remove them. After uninstalling, search the theme code for the app’s name and remove any {% render 'app-name' %} lines, script tags or snippet files it left behind. Always work on a duplicate of the live theme and preview before publishing.

Modern apps use theme app extensions instead: app embeds (site-wide, toggled under App embeds in the theme editor) and app blocks (added to specific sections). Turn off embeds you don’t need, and prefer app blocks placed only on the templates that use them. Many features that used to need apps, like size charts, specs tabs and FAQs, can now be built with metafields and metaobjects at zero performance cost.

Fix 2: Serve the right image size

Shopify’s CDN can resize and convert images on the fly (including to modern formats like WebP and AVIF where the browser supports them), but only if the theme asks for sensible sizes. The image_url and image_tag filters do the heavy lifting: give the browser a set of widths and a sizes hint, and it downloads the smallest image that looks sharp.

{%- comment -%}
  Product grid card: lazy-load, responsive widths.
{%- endcomment -%}
{{
  product.featured_image
  | image_url: width: 900
  | image_tag:
    widths: '300, 450, 600, 900',
    sizes: '(min-width: 990px) 25vw, (min-width: 750px) 33vw, 50vw',
    loading: 'lazy',
    alt: product.featured_image.alt | default: product.title
}}

The one image you should not lazy-load is the main image at the top of the page, because it is usually the Largest Contentful Paint (LCP) element. Lazy-loading it delays the moment the page looks ready. Load it eagerly and give it high priority instead:

{%- comment -%}
  Main product image: above the fold, so load it first.
{%- endcomment -%}
{%- assign hero = product.selected_or_first_available_variant.featured_image | default: product.featured_image -%}
{{
  hero
  | image_url: width: 1500
  | image_tag:
    widths: '500, 750, 1000, 1500',
    sizes: '(min-width: 990px) 55vw, 100vw',
    loading: 'eager',
    fetchpriority: 'high',
    alt: hero.alt | default: product.title
}}
  • Upload sensible originals. Shopify resizes on the fly, but a 6,000-pixel, 8 MB upload still slows processing and editing. Around 2,000–2,500 pixels on the long edge is plenty for most product photos.
  • Always output width and height (the image_tag filter does this for you), so the layout doesn’t jump while images load. Layout shift hurts your CLS score.
  • Be careful with hero videos and sliders. An autoplaying background video or a five-slide carousel at the top of the home page is often the heaviest thing in the whole store. One strong static image usually converts just as well.

Fix 3: Load JavaScript without blocking

A plain <script src> in the <head> stops the browser from building the page until that file is downloaded and run. Theme scripts should almost always use defer, which downloads them in parallel and runs them after the HTML is parsed, in order:

<!-- Blocks rendering: avoid -->
<script src="{{ 'product-form.js' | asset_url }}"></script>

<!-- Downloads in parallel, runs after parsing: prefer -->
<script src="{{ 'product-form.js' | asset_url }}" defer></script>

Beyond that, load scripts only where they’re used. If a script powers one section, include it from that section’s Liquid file instead of theme.liquid; it then loads only on pages that contain the section. And question every library: jQuery, slider plugins and animation libraries add weight that modern browser APIs can often replace. I go through the modern alternatives in modern JavaScript for WordPress and Shopify developers.

Fix 4: Fonts and third-party tags

Fonts

Each font family and weight is another download. Stick to one or two families and only the weights you actually use. Shopify’s font library fonts are served from Shopify’s own CDN; load them with font-display: swap so text appears immediately in a fallback font:

{%- comment -%} In the <style> block of theme.liquid {%- endcomment -%}
{{ settings.type_body_font | font_face: font_display: 'swap' }}
{{ settings.type_header_font | font_face: font_display: 'swap' }}

{%- comment -%} Preload only the body font, which is used above the fold {%- endcomment -%}
<link rel="preload"
      href="{{ settings.type_body_font | font_url }}"
      as="font" type="font/woff2" crossorigin>

Third-party tags

Chat widgets, heatmaps, review carousels and several ad pixels all compete for the browser’s main thread, which hurts Interaction to Next Paint (INP): the page feels sluggish when shoppers tap buttons. Practical steps:

  • Use Shopify’s built-in customer events and sales channel integrations for marketing pixels instead of pasting tracking code into the theme.
  • Load chat widgets on interaction (when the shopper clicks a “Chat with us” button) rather than on page load.
  • Remove tags for campaigns that ended. Old pixels are the most common forgotten weight.

Fix 5: Efficient Liquid

Liquid runs on Shopify’s servers, so slow Liquid shows up as a slower first byte. It rarely matters on a clean theme, but heavily customized themes can do surprising amounts of work per request. Watch for these patterns:

{%- comment -%} Avoid: looping over a whole collection to find a few items {%- endcomment -%}
{%- for item in collections.all.products -%}
  {%- if item.tags contains 'featured' -%} ... {%- endif -%}
{%- endfor -%}

{%- comment -%} Better: a dedicated collection, with a limit {%- endcomment -%}
{%- for item in collections['featured'].products limit: 8 -%}
  {%- render 'product-card', product: item -%}
{%- endfor -%}

{%- comment -%} Paginate long listings instead of rendering everything {%- endcomment -%}
{%- paginate collection.products by 24 -%}
  {%- for item in collection.products -%}
    {%- render 'product-card', product: item -%}
  {%- endfor -%}
  {{ paginate | default_pagination }}
{%- endpaginate -%}
  • Use render (isolated, cache-friendly) rather than the deprecated include.
  • Avoid nested loops over large collections, and use limit wherever you only need a few items.
  • Keep the number of sections per page reasonable. A home page with 25 sections is slow to render and slow to scroll.

What results to expect

Every store is different, so I won’t promise numbers. But to show the shape of a typical improvement, here is an illustrative example of what an audit like this often achieves on an app-heavy product page:

Illustrative example, not measured data. Before and after an optimization pass on a product page: LCP from 4.2 to 2.3 seconds, JavaScript from 980 to 410 kilobytes, requests from 145 to 72, and apps loading on the page from 9 to 4. Lower is better for all four.BeforeAfterIllustrative example, not measured dataLCP (seconds)Before: 4.2 sAfter: 2.3 s4.22.3JavaScript (KB)Before: 980 KBAfter: 410 KB980410RequestsBefore: 145 requestsAfter: 72 requests14572Apps on the pageBefore: 9 appsAfter: 4 apps94Lower is better on all four. Each panel has its own scale.Example values for illustration; real results depend on the theme, apps and content.
Figure 3: Illustrative before and after on an app-heavy product page. These are example values, not measurements from a specific store.

The pattern matters more than the numbers: removing and limiting apps cuts JavaScript and requests the most, while image fixes move LCP the most. Re-test with real-user data a few weeks after changes go live, since that’s what Google uses for Core Web Vitals.

FixMain metric it helpsEffort
Uninstall unused apps and clean leftover codeINP, total JavaScriptLow
Limit app embeds to the pages that need themINP, requestsLow
Eager-load the hero image, lazy-load the restLCPLow
Responsive image_tag widths and sizesLCP, page weightMedium
Defer theme scripts, load per sectionLCP, INPMedium
Fewer font families and weightsLCP, CLSLow
Load chat and widgets on interactionINPMedium
Refactor heavy Liquid loopsServer responseMedium
Where to start: low-effort fixes with high impact first.

Key takeaways

  • Shopify’s servers are fast; slow stores are almost always a frontend problem: apps, images, scripts and tags.
  • Measure with real-user data and PageSpeed Insights before changing anything, starting with the product page on mobile.
  • An app audit is usually the biggest single win. Remove leftover code and replace simple apps with metafields.
  • Use image_tag with widths and sizes, lazy-load below the fold and never lazy-load the hero image.
  • Defer scripts, trim fonts and load chat or widgets only when they’re needed.

Speed work pays off twice: shoppers get a smoother store, and your theme becomes simpler and easier to maintain. If your Shopify store feels slow and you’d like a proper audit with a prioritized fix list, get in touch. If you’re building or extending a theme, my Shopify theme development guide is a good companion to this one.

Code examples in this post are free to use under the MIT licence. Article text © 2026 Farooq Bin Munir.