lutiq

research

A 98 KB hero image took 18.8 seconds to load on slow 4G

By Colin Behr · · research

"Compress your images" is usually the first page speed tip anyone gives. The Shopify product page we benchmarked this month had already done that. Its hero image was about 98 KB, which is a sensible size, and on slow 4G it still took 18.8 seconds to show up.

The problem was everything else on the page. On Wi-Fi it made 571 requests and moved 12.8 MB, so on a slow connection the hero image was stuck in line behind app scripts, review widgets, tracking pixels, fonts and a popup. And the usual speed metric didn't catch it. On four of five slow 4G runs, largest contentful paint reported a block of description text at about 1.6 seconds, so on paper the page looked fast.

How we tested it

We loaded one live product page from a DTC brand's Shopify store in Chromium with a 412 × 915 Android profile and a 2× CPU slowdown. We restarted the browser and turned off the cache before each run, and did five cold runs on each of four network profiles, so 20 in total. These are medians:

ProfileFirst contentLargest content (as reported)Hero image loadedWindow loadMeta pixel PageViewRequestsTransferred
Wi-Fi0.41 s0.58 s0.58 s3.57 s3.05 s57112.8 MB
Fast 4G0.63 s3.29 s3.26 s6.55 s5.81 s57712.8 MB
Slow 4G1.59 s1.59 s18.76 s38.09 s33.46 s4426.7 MB
3G3.46 s38.22 s38.21 snot reachednot reached3664.1 MB

On the slower profiles we only counted requests and bytes inside the observation window (45 seconds for slow 4G, 65 for 3G), so treat those as lower bounds. The page hadn't finished loading.

The hero image was waiting in line

98 KB is fine. On Wi-Fi that image loaded in under a second. On slow 4G it took 18.8 seconds because it was one request among hundreds, all sharing a connection that moves about 200 KB a second.

So compressing it more wouldn't have helped. What helps is starting fewer requests before the hero and starting them in a better order: fewer scripts, later scripts, and a hero image the browser can find and prioritize early. For example, put fetchpriority="high" on the image and keep anything heavy out of the HTML ahead of it.

LCP measured the wrong thing

On four of the five slow 4G runs, the largest contentful paint entry was a block of product description text at about 1.6 seconds. On those same runs the hero image finished at about 18.75 seconds. So a dashboard reading LCP would have scored this page as fast while shoppers were looking at a product page with no product photo.

LCP measures the largest thing painted so far. If the image is stuck, that can be text, a popup or whatever shows up first. On product pages I'd time the hero image separately, from its resource timing entry or an element-timing attribute on the image, and look at it next to LCP.

A lot of the theme's JavaScript had nothing to do

Next we went through every script on the page and matched it against the page's markup to see what it was actually for. Several of them had no job on this page:

None of this is unusual. Apps get uninstalled or swapped out, themes get migrated, and the old scripts stay in the theme files. Each one is another request competing with the hero image for the same connection.

Tracking tags wait in the same line

The store's Meta pixel sent its PageView at 3.05 seconds on Wi-Fi, 33.46 seconds on slow 4G, and not at all within 65 seconds on 3G. The store's attribution tag, loaded in the page head, sent its first event at 0.22 to 0.53 seconds on every profile. So on slow 4G those two tools recorded the same visit about 33 seconds apart. I wrote more about this in a tracking tag that didn't fire until 8 to 58 seconds into the visit.

Three build mistakes that make "fast" pages slow

We've also run into these on landing pages that were built to be fast, and fixed them:

  1. Fonts inlined as base64 inside the CSS. One page had 626 KB of CSS in its head, including seven font files encoded as base64. The hero image tag ended up about 674 KB into the HTML, compared with about 53 KB on the page it replaced, so the browser couldn't even find the image until most of the document had arrived. Serve fonts as separate, cacheable files and keep the head small.
  2. Images served with cache-control: no-store. Every visit fetched every image from the origin again. The exact same hero image took 0.30 seconds to first byte from the landing page's origin, against 0.08 seconds from the store's CDN, which cached it. Give static images long cache lifetimes and serve them from a CDN.
  3. Video without byte-range support. Safari starts loading an MP4 by asking for the first two bytes and expects a 206 Partial Content response. The server answered with a 200 and the whole 28 MB file, and Safari showed the video as failed while Chrome played it fine. If you host your own video, check that Range requests come back as 206 with a Content-Range header.

One more thing we learned about where speed work pays off. If you put a light landing page in front of a heavy store, you've just moved the wait to the next tap. On one page, measured on an unthrottled connection, tapping through took 0.37 seconds on the store's own page, which swapped the view in place, and 2.4 seconds from the landing page, which had to load the store from scratch. So I'd time the whole path to the product page, starting from the ad click.

How to audit your own product page

  1. Load it cold on a throttled phone profile. In Chrome DevTools, pick a mid-range Android device and Slow 4G, and turn off the cache. Write down the request count and when the hero image finishes, from the Network panel.
  2. Time the hero separately from LCP. Check which element the LCP entry points at. If it's text or a popup, your LCP number isn't measuring the product photo.
  3. Go through your scripts. For each one, figure out what it acts on. The Coverage panel in DevTools shows how much of each file ran. Scripts from uninstalled apps, libraries nothing calls and features the page doesn't show can usually go.
  4. Check your tag triggers. Measurement tags that fire on window load inherit every delay above.
  5. Check how the page is built: no base64 fonts in the CSS, long cache lifetimes on images, fetchpriority="high" on the hero, and 206 responses for video range requests.

A few caveats

This was lab emulation on desktop Chromium with a CPU slowdown, not a real phone, and it covers one store's product page in September 2026. The popup's timing varied between runs and could explain some of the LCP variation. We also didn't measure how much time each unused script costs on its own, only that it had nothing to do on the page.

FAQ

Why is my Shopify product page slow when my images are already compressed?

On mobile networks, how many requests you make matters as much as how big they are. App scripts, review widgets, tracking pixels and fonts all compete with the hero image for the same connection. On the page we tested, a 98 KB hero took 18.8 seconds on slow 4G for exactly that reason.

Is LCP a reliable metric for product pages?

It measures the largest element painted, and that isn't always the product image. On slow connections, text or a popup can become the LCP element while the photo is still loading. I'd track the hero image's own load time as well.

How do I find unused JavaScript in a Shopify theme?

Use the Coverage panel in Chrome DevTools to see how much of each script runs, then match each script to the elements it controls. If a script's elements aren't on the page, like widgets from apps you've uninstalled, it can usually come out of the theme.

Why does my video play in Chrome but not in Safari?

Safari requests video in byte ranges and expects 206 Partial Content responses. If the server ignores the Range header and sends the whole file with a 200, Safari may not play it. Most hosting platforms handle this for you. Custom servers and edge workers often don't.


I'm Colin Behr, co-founder of Lutiq. Before Lutiq I spent years in mobile ad tech at AppLovin, Vungle and Branch. Lutiq predicts which on-brand landing page to show each paid click and measures the lift against a live holdout. The figures in this post are our own lab measurements from September 2026 unless noted, and the brands aren't named.