Conversion optimization

Site speed is an ordering problem, and a Lighthouse score will not tell you where

A slow store loses orders at specific moments: the product image that has not appeared, the add-to-cart tap that does nothing for a second, the checkout that stalls on 4G. What to measure on the pages where buying happens, what to ignore, and the order in which to fix the usual causes.

By Rehan Idrisi · · 7 min read

Part of: Conversion Optimization

A store owner asks us to make the site faster and shows a Lighthouse score of 61. The score is not the problem. The problem is that a buyer in Indore on a mid-range Android phone opens a product page from an Instagram ad, waits for the main image, taps add-to-cart before the page has finished loading, sees nothing happen, taps again, and closes the tab. None of that is visible in a desktop score run from an office connection.

Speed work on a store pays off when it is aimed at the three or four moments where slowness costs an order. Everything else is tidying. This guide is about finding those moments, measuring them the way the buyer experiences them, and fixing the causes in the order that moves the product page first.

Measure the product page, on a phone, on the network the buyer has

The home page is where speed gets measured and the product page is where orders are lost. Paid traffic and search traffic both land on product pages, so that template is the one to instrument. Two numbers matter there.

The first is Largest Contentful Paint, the moment the main product image is on screen. Until then the buyer is looking at a title and a price with a grey box, and cannot decide anything. The second is Interaction to Next Paint on the add-to-cart button: how long between the tap and something changing on screen. A button that takes a full second to respond reads as broken, and the second tap it provokes adds a duplicate line to the cart.

Take both readings from real users, not from a lab. Chrome's field data in Search Console and the CrUX dataset report them per URL for actual visitors on actual networks. Where you need a lab run, throttle it to a 4G profile and a mid-range CPU, because a tier-2 city on a shared cell tower at 8pm is nothing like an office fibre line, and the product page that feels instant on the latter can take five seconds on the former.

What to ignore

A desktop Lighthouse score. It runs once, from a fast machine, on a home page, and it blends a dozen audits into one number that goes up when you fix things no buyer notices. A store can move that score a long way without a single buyer having a faster experience. Treat it as a list of hints, never as the target.

Total page weight in isolation is the other distraction. A heavy page that paints its product image early and responds to taps is a better store than a light page that blocks on a font file. Weight matters where it delays one of the two moments above, and nowhere else.

Fix in this order: the product image, then whatever blocks first render, then the scripts that hold up the add-to-cart tap. Each step is measured on the product page, on a throttled phone, before the next one starts.

First fix: the product image is the largest thing on the page

On almost every store we have opened, the main product image is the Largest Contentful Paint element, and it is being served at the size it was photographed. A 3,000-pixel JPEG from the studio, several megabytes, delivered to a 390-pixel-wide phone screen. The buyer downloads an image more than seven times wider than the screen can show and waits the whole time.

Resize the image to the widths the template displays it at and let the browser pick with a srcset. Convert the resized copies to WebP or AVIF, keeping a JPEG fallback where an old browser needs one. Set explicit width and height attributes so the layout does not jump when the image arrives. Mark the main image with fetchpriority high and never lazy-load it, and lazy-load every gallery image below it. Do this on the product template once and it applies to every product.

<img src="/img/p/sku-0042-800.webp"
     srcset="/img/p/sku-0042-400.webp 400w,
             /img/p/sku-0042-800.webp 800w,
             /img/p/sku-0042-1200.webp 1200w"
     sizes="(max-width: 600px) 100vw, 50vw"
     width="800" height="1000"
     fetchpriority="high"
     alt="Navy cotton shirt, front view">

The Image Resizer produces the fixed-width copies from one original, and the Image Converter turns each into WebP with a before-and-after size shown, which is the quickest way to see what a single product image was costing.

Second fix: fonts and stylesheets that block the first paint

A page cannot show any text until it has the stylesheet, and by default it will not show text in a web font until that font file arrives. A theme that loads three font families in four weights each has asked the phone to download twelve files before the price appears. Cut to the weights the template uses, self-host them instead of pulling from a third-party origin, and set font-display to swap so text renders in a fallback face while the web font loads.

Do the same audit on CSS. Theme frameworks ship one stylesheet covering every page type; the product page needs a fraction of it. Inline the rules the first screen depends on and defer the rest. Preconnect to the one or two origins the page cannot avoid, the payment gateway and the image CDN, so the handshake is done before the request is made.

Third fix: the marketing scripts that hold the main thread

The add-to-cart tap is slow because the phone's single main thread is busy running JavaScript that has nothing to do with the cart. Open the network panel on a typical store and count the third-party origins: analytics, a pixel per ad platform, a heatmap recorder, a chat widget, a reviews widget, a pop-up tool, a push-notification prompt. Each was installed in a minute from a tag manager and each executes on every page view.

List every script with who asked for it and what decision it feeds. The ones nobody can answer for get removed. The ones that stay load after the page is interactive, with defer or through the tag manager's own trigger, so none of them run before the buyer can tap. The heatmap recorder and the chat widget are the usual heaviest; load them on a delay or on first scroll, and the tap responds.

Fourth fix: the apps and plugins that were never uninstalled

On Shopify and WooCommerce stores the third-party script problem has a second layer. Every app or plugin trialled and abandoned may have left its script in the theme, still loading on every page and still doing nothing. We have found upsell widgets from apps uninstalled a year earlier, still injecting their CSS.

  1. Export the list of installed apps or plugins and mark each as in use, unsure, or dead.
  2. Remove the dead ones, then search the theme files for any script tag or snippet they left behind.
  3. For each one marked unsure, disable it for a week on a staging copy and see who notices.
  4. Re-measure the product page on the throttled phone profile after each removal, so you know which one was costing you.

The order matters because each fix changes what the next measurement shows. Removing a script may make the font the new bottleneck, or the image fix may reveal a slow app that was hidden behind the image download. Measure after every step, and stop when the product page paints its image and answers a tap on a throttled 4G profile in the time a buyer will give it.

Why is my online store slow on mobile even though the Lighthouse score is fine?

A desktop Lighthouse run measures the home page from a fast connection; buyers open product pages on mid-range phones over 4G. Measure Largest Contentful Paint and add-to-cart responsiveness on the product page with a throttled mobile profile, then fix oversized product images, render-blocking fonts, marketing scripts and leftover app code, in that order.

Share this guideLinkedInXWhatsAppFacebook
All guides