Free guide · about 2 hours on a real store

Make a Shopify store fast

Let's speed up a Shopify store together, one step at a time. I will tell you what to open, what to look for, what to change, and how to check it worked. Written for the Horizon theme.

Before we start

Think of a slow store like a shop with a jammed door. People walk up, push, wait… and leave. Our job today is to find what is jamming the door, and remove it.

We will do it in 10 small steps. After every step, you check that it worked before moving on.

You needWhy
A Shopify store using HorizonAll click paths here are for Horizon
Google ChromeFor DevTools and Lighthouse
A copy of your live themeSo you never break the real store
A notebook (or a spreadsheet)To write the numbers before and after

Three words you will hear a lot

  • LCP (Largest Contentful Paint): how fast the biggest thing on the screen shows, usually the hero image or the product photo. Good: 2.5 seconds or less.
  • CLS (Cumulative Layout Shift): how much the page jumps while it loads. You know the feeling: you go to tap a button and it moves. Good: 0.1 or less.
  • INP (Interaction to Next Paint): how fast the page reacts when you tap or click. Good: 200 ms or less. (It replaced the old "FID" score.)

Step 1: Measure first (your "before" photo)

Before a diet, you step on the scale. Same here: we measure first, so we can prove the work helped.

Pick 4 pages

Test the 4 pages that make money:

  1. The home page
  2. One collection page
  3. One product page
  4. The cart page

Run the tests

  1. Open pagespeed.web.dev (opens in a new tab), paste your home page link, and click Analyze.
  2. Look at the Mobile tab first. Most shoppers are on phones.
  3. Repeat for the other 3 pages.
  4. Also open the page in Chrome, press F12, open the Lighthouse tab, pick Mobile, and click Analyze page load.

Write these 5 things down

PageScoreLCPCLSWhat is the LCP element?
Home
Collection
Product
Cart

The LCP element is listed in Lighthouse under Largest Contentful Paint element. It is usually the hero image or the main product photo. Remember it. We fix it in Step 9.

Also check Shopify's own report from real shoppers: open Shopify admin → Online Store. The Web performance card shows LCP, INP and CLS. Click a number to see the full report. (You can also find it in Analytics → Reports: search for contentful.)

Check: You have a table with the score, LCP and CLS for 4 pages, and you know the LCP element of each page.

Step 2: Make the images lighter

Images are usually the heaviest part of a store. A 3 MB banner is like carrying a suitcase when you only need a wallet.

Find the heavy images

  1. Open the page in Chrome and press F12.
  2. Click the Network tab, then the Img filter.
  3. Reload the page.
  4. Click the Size column to sort from biggest to smallest.

Aim for these sizes

ImageGood size
Desktop bannerunder 250–300 KB
Mobile bannerunder 150–200 KB
Product photoabout 150–250 KB

Before uploading, compress big images with a free tool like squoosh.app (opens in a new tab). Use JPG or WebP for photos. Use PNG only for logos or images that need a see-through background.

What Horizon already does for you

Good news: Horizon already makes smaller copies of every image for phones (this is called srcset), and it already lazy-loads images lower on the page. So for Horizon's own sections, uploading a good image is enough.

In your own custom code

If you write your own section or Custom Liquid block, print images like this:

Any custom section or block
{{ product.featured_image | image_url: width: 1200 | image_tag:
  widths: '375, 750, 1100',
  sizes: '(min-width: 990px) 50vw, 100vw',
  loading: 'lazy' }}
  • image_url: width: 1200 asks Shopify for a resized copy, not the huge original.
  • widths and sizes let the phone pick a small copy and the laptop a bigger one.
  • loading: 'lazy' means "load it only when the shopper scrolls near it".
Check: Sort the Img list by size again: no image above the fold is bigger than about 300 KB.

Step 3: Clean up apps and outside scripts

Every app can add its own code to every page, even apps you stopped using. It is like old furniture left in a small room.

Remove what you do not use

  1. Open Shopify admin → Apps.
  2. For each app, ask: "Did we use this in the last month?" If not, uninstall it.
  3. Open Online Store → Themes → Customize → App embeds (the puzzle icon on the left). Turn off embeds you do not need.

Find leftover code

Uninstalled apps sometimes leave code behind.

  1. Open Online Store → Themes → next to your copy click ⋯ → Edit code.
  2. Open layout/theme.liquid.
  3. Search (Ctrl + F) for <script.
  4. For each script, ask: "Which app or tool is this? Do we still use it?"
  5. Also look in the snippets folder for files named after old apps.

If you still need a script but it is not important for the first screen (heatmaps, chat, extra trackers), add defer:

layout/theme.liquid
<script src="https://example.com/widget.js" defer></script>

defer means: "download it in the background and run it after the page is ready". The shopper sees the page first.

Replace heavy apps with theme features

Horizon already has many things people install apps for:

  • Trust icons and payment logos → the Icon and Payment icons blocks
  • Low stock message → the Product inventory block
  • Before/after slider → the Comparison slider block
  • Simple text badges and popups → a Custom Liquid block with a few lines
Check: Network → JS (reload the page): the list of scripts from other websites is shorter than before.

Step 4: Keep CSS small

CSS is the paint and decoration of the page. The browser waits for it before it shows anything, so less is better.

  1. Press F12 → Network → CSS, and reload.
  2. Look for CSS files from old apps (their names often include the app name). Remove the code that loads them in layout/theme.liquid or the app's snippet.
  3. In your own blocks, put CSS inside a {% stylesheet %} tag at the bottom of the block file, the same way Horizon does. Shopify then loads it only where the block is used.
blocks/your-block.liquid (bottom of the file)
{% stylesheet %}
  .usp-list { margin: 0; padding-left: 18px; }
{% endstylesheet %}

Use the theme's colours

Do not type colours like #ff0000 again and again. Horizon turns your Theme settings → Colors into CSS variables. Use them, so one change updates the whole store:

Your custom CSS
.my-badge {
  background: var(--color-primary-button-background);
  color: var(--color-primary-button-text);
}

If a variable name does not work, open snippets/theme-styles-variables.liquid to see the exact names in your Horizon version.

Check: Network → CSS shows no files from apps you removed.

Step 5: Keep JavaScript light

JavaScript is what makes things move: sliders, popups, the cart drawer. It is also the main reason a page feels "stuck" when you tap (that is INP).

Find the big files

  1. Press F12 → Network → JS, and reload.
  2. Sort by Size.
  3. For each big file, ask: "Which feature is this? Do we need it on this page?"

Simple rules

  • Remove sliders, galleries and popups nobody uses.
  • One tool per job. Three different slider libraries for three sliders is waste. Use one.
  • Load your own scripts with defer:
layout/theme.liquid
<script src="{{ 'custom.js' | asset_url }}" defer></script>
  • Send only the data you need. Do not print a huge JSON of every product into the page when the script needs only the title and price.
  • No jQuery. Horizon does not use it. If an old script needs jQuery, rewrite it in plain JavaScript or remove it.
Check: Lighthouse → "Reduce unused JavaScript" shows a smaller number than in Step 1.

Step 6: Fonts

Every font style is a separate download. Five fonts with four weights each is twenty downloads before the text looks right.

Keep it simple

  • 1 or 2 font families.
  • 2 or 3 weights (for example regular, medium, bold).

In Horizon, pick fonts in Customize → Theme settings → Typography. Shopify hosts these fonts and Horizon already loads them the fast way, so the text shows straight away in a backup font, then swaps.

If you upload your own font

Upload the .woff2 file in Edit code → Assets. Then open layout/theme.liquid and add this inside <head>. It must be in a .liquid file, because asset_url is Liquid:

layout/theme.liquid (inside <head>)
<style>
  @font-face {
    font-family: 'BrandFont';
    src: url('{{ "brand-font.woff2" | asset_url }}') format('woff2');
    font-display: swap;
  }
</style>

font-display: swap means "show the text right away in a normal font, then switch to mine when it arrives". The shopper never stares at blank space.

To start the download early, add this inside <head> in layout/theme.liquid:

layout/theme.liquid
{{ 'brand-font.woff2' | asset_url | preload_tag: as: 'font', type: 'font/woff2', crossorigin: 'anonymous' }}

Preload only one font file, the one used in the first screen. Preloading many slows things down again.

Check: Network → Font shows 4 font files or fewer.

Step 7: Clean up Liquid and sections

Liquid runs on Shopify's server before the page is sent. Heavy loops make the server work longer, so the first byte arrives later.

What to look for

  • Big loops over everything. This line reads every product in the store:
Avoid this
{% for product in collections['all'].products %}

Use a small, named collection with a limit instead:

Do this
{% for product in collections['best-sellers'].products limit: 8 %}
  • all_products['handle'] used many times. Each one is a lookup. Use a product list metafield or a collection instead.
  • Sections nobody uses. In each JSON template (Customize → each page type), remove hidden sections. A hidden section still sits in the template.
  • Old files. In sections/ and snippets/, files from old apps or old designs can go. Before deleting, search the theme for the file name to make sure nothing uses it.
Check: Theme Check shows no errors, and every template has only the sections you can see on the page.

Step 8: Let Shopify do the heavy lifting

Shopify stores the finished pages close to your shoppers, all over the world. Help it:

  • Keep pages the same for everyone. Things like a random product on every load, or the exact current time printed by Liquid, make each page different and harder to reuse.
  • Apps: do the work in the app, not in the browser. If an app needs to calculate something big, it should do it on its own server and send only the final answer to the store.
  • Load extra parts only when needed. For example, load "recommended products" only when the shopper scrolls near them. Horizon already does this for its Product recommendations block.
Check: Your custom code has no random or time-based Liquid in sections that show on every page.

Step 9: Fix the LCP image (the biggest win)

Remember the LCP element you wrote down in Step 1? This is usually the step that moves the score the most.

  1. Make the image light. Compress it to under about 300 KB (Step 2).
  2. Do not lazy-load it. It is at the top, so it must load right away.
  3. Use one image, not a slideshow. A slideshow waits for JavaScript before it shows the first slide. In Customize → Home page, use a single Hero or Image section at the top.
  4. Keep it high in the page. Do not put heavy sections (videos, sliders, app widgets) above it.

In your own hero code, tell the browser this image is important:

Your custom hero section
{{ section.settings.image | image_url: width: 1800 | image_tag:
  loading: 'eager',
  fetchpriority: 'high',
  widths: '750, 1100, 1500, 1800',
  sizes: '100vw' }}
  • loading: 'eager' means "load now".
  • fetchpriority: 'high' means "this one first, before other images".
Check: Lighthouse no longer warns "Largest Contentful Paint image was lazily loaded", and LCP is lower than in Step 1.

Step 10: Measure again and go live

Time for the "after" photo.

  1. Run PageSpeed Insights and Lighthouse on the same 4 pages, on your theme copy (use the theme's Preview link).
  2. Fill the same table again, 3 runs each, middle score.
  3. Compare before and after.
  4. Click through the store like a shopper: add to cart, open the cart, change quantity, go to checkout. Make sure nothing broke.
  5. When everything works, publish the copy: Online Store → Themes → your copy → Publish.
  6. Check the Web performance card in Shopify admin → Online Store again after a few days. It uses real shoppers, so it updates slowly.
PageScore beforeScore afterLCP beforeLCP after
Home
Collection
Product
Cart
Check: Every page is faster than before, nothing is broken, and the new theme is published.

Common mistakes

  • Testing the live theme while you edit. Always work on a copy.
  • Testing once. Lighthouse moves up and down. Use the middle of 3 runs.
  • Lazy-loading the hero image. It makes LCP worse, not better.
  • Deleting a file without searching for it first. Search the theme for the file name before you delete it.
  • Chasing 100. A real store with real apps rarely gets 100. Aim for clearly better LCP, CLS and INP.

Quick recap

  1. Measure 4 pages first and write the numbers down.
  2. Make images light. Horizon handles sizes for you.
  3. Remove unused apps and leftover scripts. Defer the rest.
  4. Keep CSS small and use the theme's colour variables.
  5. Keep JavaScript small. One tool per job, defer, no jQuery.
  6. Use 1–2 fonts with font-display: swap.
  7. Avoid big Liquid loops and unused sections.
  8. Keep pages the same for everyone, so Shopify can serve them fast.
  9. Fix the LCP image: light, not lazy, no slideshow, high priority.
  10. Measure again, test like a shopper, then publish.

Want me to look at your store with you? Book a live class and we will go through these steps on your own theme.