Shopify speed optimization: from 4.8s to 1.2s, and what actually did it
A concrete breakdown of the changes that took a Shopify storefront from 4.8s to 1.2s — rendering strategy, image discipline, third-party scripts and font loading.
Every store owner has been told their site is slow, and most have been handed a PageSpeed screenshot as evidence. The screenshot is not the problem. The problem is that page speed work has a long tail of changes worth 20ms each, and a short head of changes worth whole seconds — and the two are usually addressed in the wrong order.
MioMio went from 4.8s to 1.2s. That is four times faster on mobile. Here is what was in the head of that distribution, roughly in order of what it returned.
1. Rendering strategy — the largest single win
The legacy storefront rendered on the client. A visitor downloaded an HTML shell, then a JavaScript bundle, then waited for the bundle to request the catalogue, then waited again for the images that request implied. Four sequential round trips before anything meaningful appeared.
Server rendering collapses that into one. The first response already contains the product grid, the copy and the image URLs. The browser can start fetching images while JavaScript is still downloading, because it can see them in the markup.
This is not a micro-optimisation, and no amount of bundle trimming substitutes for it. A client-rendered catalogue page has a floor set by its own waterfall depth. Server rendering removes the floor.
2. Images — the largest byte win
On a fashion catalogue, images are roughly 80% of transferred bytes. Three changes, in descending order of value:
- →Modern formats. WebP against optimised JPEG is a 25–35% reduction at visually identical quality. It is a build-step change, not a design change.
- →Correct dimensions. Serving a 2000px master into a 400px slot wastes roughly 96% of the pixels. Responsive srcset per breakpoint fixes it once, globally.
- →Honest lazy loading. Everything below the fold gets loading="lazy". Everything above it explicitly does not — lazy-loading your hero image is a self-inflicted LCP penalty, and it is a common one.
Always pair this with explicit dimensions or an aspect-ratio. Images that arrive without reserved space cause layout shift, and CLS is the cheapest Core Web Vital to fix and the most commonly left broken.
<img
src="/hero.webp"
alt="Autumn campaign"
width="1600" height="900"
fetchpriority="high"
/>3. Third-party scripts — the invisible tax
A typical Ukrainian storefront carries analytics, a CRM widget, a chat widget, a pixel or two, and a review platform. Individually each is 'lightweight'. Together they routinely account for more main-thread blocking time than the entire application.
The stack here runs them through Partytown, which relocates third-party scripts into a web worker. The main thread stops competing with a chat widget for the right to render the page. Where a script cannot be moved off-thread, it is deferred until after first interaction.
Before reaching for tooling, run the free version of this audit: list every third-party script, and for each one name the person who will notice if it disappears. Scripts without an owner are the fastest performance win available, because deleting them costs nothing.
4. Fonts — small bytes, large blocking
Web fonts are rarely a bandwidth problem and frequently a blocking one. A font loaded through a CSS @import is discovered only after the stylesheet parses, which puts a network round trip in series with rendering.
- →Preconnect to the font origin so the TLS handshake happens in parallel rather than on demand.
- →Load fonts via <link> in the document head, not @import inside CSS.
- →Use font-display: swap so text renders immediately in a fallback rather than holding the paint hostage.
- →Subset to the character sets actually used. A Cyrillic + Latin site does not need the full Greek and Vietnamese ranges.
5. Caching and delivery
Static assets — images, fonts, hashed bundles — get a one-year immutable cache header. The hash in the filename makes invalidation automatic, so there is no reason to be conservative:
Cache-Control: public, max-age=31536000, immutableA CDN in front of that puts the bytes physically closer to the visitor. For a Ukrainian store serving Ukrainian customers this matters less than the marketing implies — but for the same store serving European traffic, it is the difference between 40ms and 300ms on every asset.
Measure the right thing
PageSpeed Insights runs a lab test on simulated hardware. It is a debugging tool, not a scoreboard. What Google actually ranks on is field data — Core Web Vitals collected from real Chrome users on real devices and real networks.
The two diverge constantly. A lab score of 95 with failing field LCP means real users are on slower hardware and worse connections than the simulation. Trust the field data, use the lab tool to find out why.
Three numbers are worth watching: LCP under 2.5s, INP under 200ms, CLS under 0.1. Everything else is diagnostic detail in service of those three.
What speed is actually worth
The ranking benefit of Core Web Vitals is real but modest — a tiebreaker between comparable results rather than a lever. The conversion benefit is neither modest nor subtle. On this project, page load falling from 4.8s to 1.2s coincided with a 25% conversion lift in the first quarter, alongside a rebrand.
That is the honest framing: speed rarely wins you rankings on its own, and reliably wins you revenue from the traffic you already have. Optimise for the second and accept the first as a bonus.