
There’s a common misconception among developers new to Shopify: if it happens on the server, it must be fast.
While Shopify’s servers are incredibly powerful, Liquid—the templating language that powers every Shopify theme—is not immune to performance bottlenecks. Inefficient Liquid code can add precious seconds to your page load time, creating a frustrating user experience and hurting your conversion rates.
One of the biggest culprits? Nested loops.
Based on real-world performance issues found in high-traffic stores, let’s dive into why nested loops are so damaging and explore two powerful techniques to refactor your code for a faster, more efficient theme.
The Hidden Cost of Nested Loops
Imagine a “super collections” page that displays twenty collections, each showing fifty products. Now, imagine each of those products has ten variants, and you need to access a metafield on each variant.
The math is terrifying: 20 collections * 50 products * 10 variants = 10,000
iterations.
If your loop is even more complex, you could easily be asking Shopify’s servers to perform hundreds of thousands of operations before the page even begins to render. This was the cause of a real-world site spending a full twenty seconds just thinking before it even started loading.
Liquid has gotten faster over the years, but the fundamental principle remains: loops, especially nested ones, are performance killers. Here’s how to avoid them.
Technique 1: Unroll Your Loops (The “Do It Ten Times” Method)
Sometimes, you have a loop that runs a small, fixed number of times. For example, generating a gallery of exactly ten images.
The Slow Way (Nested Loop):
{% for collection in collections %}
{% for product in collection.products %}
{% for variant in product.variants %}
{% comment %} This inner loop is the problem {% endcomment %}
{% for i in (1..10) %}
<img src="{{ variant.metafields.my_fields.gallery_image[i].value.src | img_url: 'small' }}" alt="">
{% endfor %}
{% endfor %}
{% endfor %}
{% endfor %}
This adds another layer of nesting and complexity. If you know the number of iterations is fixed and small, you can “unroll” the loop.
The Fast Way (Unrolled Loop):
{% for collection in collections %}
{% for product in collection.products %}
{% for variant in product.variants %}
{% comment %} Render a snippet 10 times instead of looping {% endcomment %}
{% render 'gallery-image-item', image: variant.metafields.my_fields.gallery_image[1] %}
{% render 'gallery-image-item', image: variant.metafields.my_fields.gallery_image[2] %}
{% comment %} ... and so on, up to 10 ... {% endcomment %}
{% endfor %}
{% endfor %}
{% endfor %}
By using a snippet (gallery-image-item.liquid
), you keep your code DRY (Don’t Repeat Yourself) while completely eliminating one level of nesting. This is a huge win for performance.
Technique 2: Use capture
to Avoid Looping Multiple Times
A common pattern on a Product Details Page (PDP) is to loop through the product.variants
array multiple times to render different elements: once for images, once for swatches, once for descriptions, etc.
The Slow Way (Multiple Loops):
<div class="product-images">
{% for variant in product.variants %}
<img src="{{ variant.image.src | img_url: 'large' }}" alt="">
{% endfor %}
</div>
<div class="product-swatches">
{% for variant in product.variants %}
<button style="background-color: {{ variant.option1 }};"></button>
{% endfor %}
</div>
This is inefficient. You’re iterating over the exact same array multiple times. Instead, you can loop once and capture
the HTML for each section into variables.
The Fast Way (Single Loop with capture
):
{% liquid
assign product_images = ''
assign product_swatches = ''
%}
{% for variant in product.variants %}
{% capture product_images %}
{{ product_images }}
<img src="{{ variant.image.src | img_url: 'large' }}" alt="">
{% endcapture %}
{% capture product_swatches %}
{{ product_swatches }}
<button style="background-color: {{ variant.option1 }};"></button>
{% endcapture %}
{% endfor %}
<div class="product-images">
{{ product_images }}
</div>
<div class="product-swatches">
{{ product_swatches }}
</div>
With this method, you’ve reduced multiple loops to a single one, significantly cutting down on the server’s workload. As a bonus, it often makes the overall structure of your page’s template easier to read.
Final Thoughts: Think About Performance First
A fast website is no longer a luxury; it’s a necessity. Before you write your next loop in Liquid, take a moment to think about its performance implications.
- Can this loop be unrolled?
- Am I iterating over the same data multiple times?
- Can I use
capture
to be more efficient?
By asking these questions and using tools like the Shopify Theme Inspector for Chrome, you can build themes that are not only beautiful and functional but also incredibly fast.
❓ What are your favorite Liquid performance tips?