Back to Lab
RAXXO Studios 8 min read No time? Make it a 1 min read

Shopify Theme Performance: From 62 to 98 Lighthouse in One Weekend

Core Web Vitals
8 min read
TLDR
×
  • I took raxxo.shop from a 62 Lighthouse mobile score to 98 without leaving the Liquid theme, and this is the exact punch list.
  • The biggest wins were image format, font loading strategy, and cutting app scripts, not clever tricks.
  • Shopify sections let you lazy-load whole page sections below the fold, which almost no theme uses by default.
  • Core Web Vitals on Shopify is a budget problem, not a skill problem. Set the budget, cut until you hit it.

raxxo.shop runs on a stock Shopify theme I wrote from scratch. 87 custom sections, 114 products, 186 blog posts. A month ago I ran a Lighthouse audit on the homepage and got a 62 on mobile. I was furious. The site felt fast. The site looked clean. The site was not fast. It was a 4-second LCP and a 0.21 CLS and every other metric you do not want. I spent one weekend going through every suggestion Lighthouse made, and I finished at 98. This is the Shopify theme performance checklist I wish someone had handed me, in the order the fixes mattered, with the numbers.

Shopify theme performance is a budget problem, not a skill problem. You have a finite amount of kilobytes before the user's first paint, a finite number of round trips, a finite number of JavaScript bytes the main thread can parse. When you spend those on apps, on fonts, on poorly sized images, the score drops and the user waits. When you spend them intentionally, a Shopify theme can be as fast as a hand-tuned static site. Mine is now.

The 62 Baseline

I ran the audit twice before touching anything. Mobile, slow 4G, cold cache. The numbers were:

| Metric | Before |

|---|---|

| Performance score | 62 |

| LCP | 4.1s |

| FCP | 2.3s |

| CLS | 0.21 |

| TBT | 680ms |

| Speed Index | 3.8s |

The biggest red flags from the Treemap report were telling. 780 KB of JavaScript before any app was even loaded. A 310 KB hero image served as a full-resolution JPEG. Three font families loaded synchronously, each with four weights. Two Shopify apps injecting their own scripts into the head. A cumulative layout shift caused by an image without explicit dimensions.

None of this was clever engineering. It was the default install of a reasonable theme pulling in reasonable defaults. Shopify does not ship a slow product, but the defaults assume you want every feature on. If you only use a few, the unused ones are still costing you.

The 9 Changes That Got Me to 98

Here is what I did, in the order I did it. Each change shipped independently and I re-measured after every one. The order matters because some changes depend on others, and because you learn faster when you can attribute a score jump to a single action.

1. One font family, preload only

I was loading Outfit in four weights and two backup sans-serif families for fallback. Three font families, 280 KB of WOFF2. I dropped to one family (Outfit), two weights (400 and 700), and preloaded the 400 weight only.


{%- liquid
  assign font_variant = settings.type_body_font | font_modify: 'weight', '400'
-%}

Also added `font-display: swap` to every `@font-face` so text paints with a fallback while the web font loads, not after. LCP dropped from 4.1s to 3.2s on this change alone.

2. WebP and responsive images, everywhere

Shopify's `image_url` filter supports format conversion and responsive srcsets natively. I was not using either. Every product thumbnail was a full 1000x1000 JPEG regardless of the rendered size. Every hero image was a desktop-resolution file served to mobile.


{% assign img = product.featured_image %}
{{ img.alt | escape }}

The hero image dropped from 310 KB to 42 KB. Thumbnails dropped 70% in aggregate. LCP dropped another 600ms. Also notice `width` and `height` attributes: those kill CLS for image-heavy pages because the browser reserves space before the image loads.

3. Lazy-load below-the-fold sections

This one almost no theme does by default. Shopify sections can be wrapped in an `` pattern for images, but the section itself still runs its Liquid and ships its HTML. For heavy sections (reviews, related products, galleries) you can wrap them in a custom element that renders a placeholder until the user scrolls into view.


The `

4. Cut two Shopify apps

This one hurts to write. I had a review app and a popup app installed. Together they injected 340 KB of JavaScript into the head and added three external render-blocking stylesheets. I evaluated what each did and whether I used it enough to justify the cost. I did not. I replaced the review app with native Shopify reviews (free, zero JS, renders in Liquid) and the popup app with a 40-line custom modal I wrote myself.


{%- for product in collection.products -%}
  {%- if product.metafields.reviews.rating.value -%}
    
{%- for i in (1..5) -%} {%- endfor -%}
{%- endif -%} {%- endfor -%}

Apps are the number one performance tax on Shopify stores I audit. Before installing any app, check its Lighthouse impact in a throwaway dev theme. If it adds more than 50 KB of JS to your critical path, you probably do not need it.

5. Defer every non-critical script

Every script tag in my theme now has either `defer` or is loaded at the end of the body. If a script does not need to run before the first paint, it should not block the first paint. I audited the head and found three scripts that blocked rendering for reasons nobody could explain. Two were from an analytics integration, one was from a checkout tweak I had written myself three years ago. All three moved to defer.



`defer` downloads the script in parallel with parsing but executes after `DOMContentLoaded`. `async` downloads and executes as soon as possible, which can be worse for scripts with dependencies. For 95% of use cases, `defer` is the right choice. TBT dropped another 120ms.

6. Inline critical CSS, async everything else

Shopify's default theme ships one giant `theme.css` file that blocks rendering. I split mine into two files: `critical.css` with the 6 KB needed for the header, hero, and above-the-fold product grid, and `rest.css` with everything else. Critical CSS is inlined into the head. The rest loads async via a trick that tricks the browser into treating a stylesheet as print-only on load, then swapping to all when loaded.





FCP dropped from 2.3s to 1.1s. This is the single most impactful CSS change I have ever made on a Shopify theme.

7. Explicit dimensions on everything

CLS was 0.21, which is terrible. Anything above 0.1 is a fail. Going through the layout I found three culprits: product card images without width and height, a hero carousel that resized on font load, and an embedded video iframe that defaulted to zero height until the player initialized. Each got explicit dimensions.



...

CLS went from 0.21 to 0.02. The user experience of the site changed audibly: no more jumpy layout as things load in.

8. Preconnect and DNS-prefetch for third-party origins

The Shopify CDN is on a different origin (`cdn.shopify.com`), and if you use Google Fonts or any analytics, those are on other origins too. A `` hint tells the browser to open the TCP connection and do the TLS handshake before the first request to that origin, saving a few hundred milliseconds.




Small change, 80ms off LCP on slow networks. Worth it because the cost is zero.

9. Turn off the animation library I forgot about

I had installed a small animation library for one hero effect two years ago and forgotten to remove it when I redesigned. 18 KB of JavaScript loaded on every page for an effect that no longer existed. Grep is your friend. Grep every theme for `

This article contains affiliate links. If you sign up through them, I may earn a small commission at no extra cost to you. (Ad)

Stay in the loop
New tools, drops, and AI experiments. No spam. Unsubscribe anytime.
Back to all articles