Writing Liquid Templates That Render Fast

4 min read

Đọc bằng tiếng Việt →

Liquid runs on Shopify’s servers before a single byte reaches the browser, so inefficient templates show up as a slower time to first byte on every uncached request. Most themes are not dramatically slow here, but sections, blocks and snippets stack up. These are the habits we would suggest to keep template rendering lean, along with the points where Liquid is rarely the real bottleneck.

Be realistic about the payoff

Shopify’s documentation is candid about scale: individual Liquid micro-optimisations save roughly 1 to 5 ms of TTFB each, and on a theme with around 50 sections and several blocks per section, applying all the patterns together might recover something like 25 to 50 ms. That is worth having on a complex theme, but it will not rescue a storefront that ships heavy JavaScript or unoptimised images. Treat Liquid tuning as the final polish, and measure with the Theme Inspector extension for Chrome, which shows where render time goes line by line.

Pull constant work out of loops

The single most reliable rule: if an operation returns the same result on every iteration, do it once before the loop. Sorting, filtering and metafield lookups are the usual suspects.

{%- comment -%} Before: setting lookups repeat on each pass {%- endcomment -%}
{% for product in collection.products %}
  <h3 class="{{ section.settings.heading_class }}">{{ product.title }}</h3>
  {% if section.settings.show_vendor %}
    <p>{{ product.vendor }}</p>
  {% endif %}
{% endfor %}

{%- comment -%} After: read once, reuse {%- endcomment -%}
{%- liquid
  assign heading_class = section.settings.heading_class
  assign show_vendor = section.settings.show_vendor
-%}
{% for product in collection.products %}
  <h3 class="{{ heading_class }}">{{ product.title }}</h3>
  {% if show_vendor %}
    <p>{{ product.vendor }}</p>
  {% endif %}
{% endfor %}

The same thinking applies to block settings: assign them to a variable once and refer to the variable afterwards. If a filter gets called repeatedly with identical arguments, store its result instead of recomputing it.

Fetch once, filter in Liquid

When a page needs several filtered views of the same data, resist the urge to request each view separately. Shopify’s guidance notes that filtering inside Liquid is very fast, whereas every additional data request carries network and query overhead. Pull the full set once and split it with a loop or a filter.

Keep snippet nesting shallow

Each render call has a cost, and putting one inside a loop multiplies it. A product card snippet that itself renders a price snippet, which renders a badge snippet, will be invoked many times on a collection page. Where a snippet is tiny and only used in one place, inlining its markup is often cleaner than the extra layer. Deeply nested loops deserve the same suspicion, as does a mega menu that outputs a large amount of markup for every page view.

Prefer plain output over string gymnastics

Building strings with chains of filters is slower than simply printing values with output tags or echo. If you find yourself concatenating pieces of a class name with several filters, consider whether a couple of conditionals would read better and run faster.

The wins that live outside Liquid

Shopify’s broader theme guidance points at areas that usually matter more than template micro-tuning:

  • JavaScript: use HTML and CSS for core behaviour, treat scripts as progressive enhancement, keep the minified bundle at 16 KB or less, and load files with defer or async.
  • Resource hints: no more than two per template, reserved for genuinely critical, render-blocking files.
  • Images: lazy-load only below-the-fold images, request the size you will actually display, and let image_tag generate a responsive srcset.
  • Fonts: system fonts avoid extra downloads entirely.
  • Assets: serve them from the theme’s assets folder so they come from Shopify’s CDN.

A small example of the image point:

{{ product.featured_image
  | image_url: width: 800
  | image_tag:
      loading: 'lazy',
      sizes: '(min-width: 990px) 25vw, 50vw',
      widths: '300, 450, 600, 800' }}

Use loading: 'lazy' only for images below the first screen; the hero image should load normally so the browser can fetch it early.

Guard against regressions

Theme Check flags oversized CSS and JS bundles, remote asset references, parser-blocking scripts and cross-file CSS dependencies, which makes it a useful linter to run in your editor. For larger projects, Shopify also offers a Lighthouse CI action that can run on pull requests, and its docs recommend repeating Lighthouse runs and taking the median rather than trusting a single score. Adding ?pb=0 to a URL hides the preview bar during audits.

If you would like a second pair of eyes on an existing store, our services page describes how we approach theme audits, and the themes we publish are built with these constraints in mind from the start.

Further reading

← All posts