Shopify Store Speed: The Theme and Liquid Fixes I Reach For First
When a Shopify store feels slow, the cause is rarely one big problem. In my experience building and fixing stores, it is usually a pile of small ones: scripts from apps nobody uses anymore, images far bigger than the space they render in, fonts loading in a way that blocks the page, and sections below the fold doing heavy work before a visitor ever scrolls to them.
None of these need a rebuild to fix. Here are the theme and Liquid level checks I run first, in the order I run them.
1. Audit what your apps are injecting into theme.liquid
Every app that adds a widget, a popup, a review badge, or a chat bubble usually loads its JavaScript and CSS on every page, whether that page needs it or not. Over a year or two of installing and removing apps, the leftovers add up, and some removed apps leave their code behind in your theme files.
Open layout/theme.liquid and look at what is being loaded in the <head> and before </body>. Then check the {{ content_for_header }} area indirectly by viewing the page source of your live store and searching for script tags you do not recognize.
A practical way to audit:
- List every app currently installed in your Shopify admin.
- View the page source and note each third party script domain.
- Match the two lists. Anything loading that does not match an installed, active app is a candidate for removal.
- For apps you do use, check their settings. Many have an option to load only on specific page types, for example only on product pages. A reviews app rarely needs to load on your About page.
If an app left a snippet behind, you will often find it in theme.liquid or in a snippet file with the app name. Remove it, preview the theme, and test the pages the app touched before publishing.
2. Stop shipping giant images
The most common image problem I see is a store uploading a 3000px wide photo and letting the browser shrink it into a 600px slot. The visitor downloads the full file either way.
Shopify serves images through a CDN and can resize them on the fly if you ask for a size in Liquid. Instead of outputting the raw image URL, request the size you actually need:
<img
src="{{ product.featured_image | image_url: width: 800 }}"
alt="{{ product.featured_image.alt | escape }}"
width="800"
height="{{ 800 | divided_by: product.featured_image.aspect_ratio | round }}"
>
Always include width and height attributes. They let the browser reserve the right amount of space before the image loads, which stops the page from jumping around as images arrive.
For collection grids and other places where the display size changes between mobile and desktop, use srcset so the browser can pick an appropriate file:
<img
src="{{ image | image_url: width: 600 }}"
srcset="{{ image | image_url: width: 400 }} 400w,
{{ image | image_url: width: 600 }} 600w,
{{ image | image_url: width: 900 }} 900w"
sizes="(max-width: 749px) 100vw, 600px"
alt="{{ image.alt | escape }}"
loading="lazy"
width="600"
height="{{ 600 | divided_by: image.aspect_ratio | round }}"
>
The newer image_tag filter can generate much of this for you, and it is worth reaching for in new sections:
{{ product.featured_image
| image_url: width: 900
| image_tag: loading: 'lazy', widths: '400, 600, 900'
}}
3. Lazy load everything below the fold, and nothing above it
Lazy loading tells the browser to delay an image until the visitor is about to scroll to it. That is exactly what you want for collection grids, lookbooks, testimonials, and anything else below the first screen.
The rule I follow: the first visible image, usually the hero or the main product image, should load eagerly. Everything else gets loading="lazy".
{% if forloop.first %}
{% assign loading_attr = 'eager' %}
{% else %}
{% assign loading_attr = 'lazy' %}
{% endif %}
<img src="{{ image | image_url: width: 800 }}" loading="{{ loading_attr }}" alt="{{ image.alt | escape }}">
Lazy loading the hero image by mistake is an easy way to make a store feel slower, because the browser waits instead of fetching the most important image right away. For that hero image, you can also add fetchpriority="high" to tell the browser it matters most:
<img src="hero.jpg" fetchpriority="high" alt="...">
The same thinking applies to iframes. An embedded video or map below the fold should carry loading="lazy" too.
4. Load fonts without blocking the page
Custom fonts are part of a brand, but a font file the browser insists on loading before showing any text will make even a light page feel slow. If you self host fonts or load them in the theme, a few small changes help:
- Use
font-display: swapin your@font-facerules so the browser shows fallback text first and swaps in the brand font when it arrives, instead of showing nothing. - Preload only the one or two font files used above the fold, and only in the format modern browsers need, usually woff2.
<link rel="preload" href="{{ 'brand-font.woff2' | asset_url }}" as="font" type="font/woff2" crossorigin>
Preloading every weight and style defeats the point. Preload the regular weight used in headings or body text, and let the rest load normally.
5. Defer JavaScript that is not needed immediately
Theme JavaScript that runs sliders, drawers, and announcement bars does not all need to block the first render. If your theme loads custom scripts in theme.liquid, add defer where the script does not need to run before the page content appears:
<script src="{{ 'theme.js' | asset_url }}" defer></script>
Be careful with this one. Scripts that other inline code depends on can break if their order changes, so test the cart drawer, search, and product forms after any change. A safe pattern is to defer your own theme scripts and leave anything a critical inline snippet depends on until you can refactor the pair together.
A simple order of operations
If you are looking at a slow store and do not know where to start, this is the sequence I use:
- Audit app scripts in the page source and remove what is unused.
- Fix image sizes and add width and height attributes in your templates.
- Lazy load below the fold images and iframes, keep the hero eager.
- Add
font-display: swapand preload only the main font file. - Defer non critical theme JavaScript, then test the interactive pieces.
Each step is small, reversible, and testable on a duplicate theme before it goes live. That is the part I would stress: duplicate the theme, make one change at a time, preview it, and check the pages your customers actually use, especially product pages and the cart. Speed work done carelessly can break the parts of a store that make money, and a careful hour in the theme code usually beats a new app install promising to do it all for you.
I am abi, a Shopify and full-stack developer (React, Next.js) based in Dhaka and working with clients worldwide. I build stores and web apps for brands and agencies, and I offer white-label development for agencies that need a dev partner behind their brand. You can see my work at https://abdullah.itservicesbd.com/ or book a short call at https://calendly.com/mdabdullah-dev/30min.
Top comments (0)