- 9 min read
Liquid Theme Performance: Faster Without a Rewrite
When a Shopify store is slow, the theme usually gets the blame, and Liquid usually gets blamed first. But Liquid renders on Shopify's servers, and server response is rarely where the seconds go. The time is spent after the HTML arrives — in images that are too big, scripts that block rendering, and apps nobody removed.
Which is good news: most of it can be fixed without rewriting the theme.
Images, properly
The single biggest win on a product page. Ask Shopify's CDN for the size you will actually
display, give the browser a srcset, and keep loading="lazy" away from
the image that is the Largest Contentful Paint:
{%- assign img = product.featured_media.preview_image -%}
<img
src="{{ img | image_url: width: 800 }}"
srcset="{{ img | image_url: width: 400 }} 400w,
{{ img | image_url: width: 800 }} 800w,
{{ img | image_url: width: 1200 }} 1200w"
sizes="(min-width: 1024px) 50vw, 100vw"
width="{{ img.width }}"
height="{{ img.height }}"
alt="{{ img.alt | escape }}"
fetchpriority="high"
decoding="async">
The width and height attributes are not decoration. They are what
stops the page from jumping while images load, which is most of a bad CLS score.
Scripts that block the first paint
- Load theme JavaScript with
defer; a render-blocking bundle in<head>delays everything behind it. - Preload the font you actually use for the heading, and set
font-display: swap. Two weights are usually enough. - Audit installed apps. Every one of them is a script tag; the ones from a trial you abandoned are still loading.
- Use the Section Rendering API for filters and cart drawers instead of shipping a framework to re-render a list.
// Re-render one section instead of reloading the page or hydrating the whole app.
async function applyFilter(url) {
const res = await fetch(`${url}§ion_id=collection-grid`);
const html = await res.text();
const doc = new DOMParser().parseFromString(html, "text/html");
document.querySelector("#collection-grid").replaceWith(
doc.querySelector("#collection-grid")
);
history.replaceState({}, "", url);
}
The loops that quietly cost the most
Liquid is fast until it is asked to walk a catalogue. Two patterns to watch for:
- Nested loops over
collection.productsinside another loop — the cost multiplies, and it only shows up on the biggest collection. - Looping over
all_productsor every variant to compute something that could be a metafield written once.
If a value is expensive to compute and rarely changes, compute it elsewhere and store it on the product. Liquid should read, not calculate.
Measure the right thing
Lighthouse is a lab test on a simulated phone; it is useful for comparing two builds, not for telling you what real buyers experience. Use the Chrome User Experience report or your own field data for that, then use Shopify's Theme Inspector to find the Liquid that is genuinely slow — usually one snippet, not the theme.
Fix the LCP image first. On most product pages it is worth more than every other change put together.