DEV Community

Evgenii Milevich
Evgenii Milevich

Posted on

5 Shopify Liquid Tricks That Actually Boost Conversion Rate

Most Shopify Liquid tutorials stop at "here's how to render a variable." That's fine for learning, but it won't help you ship features that actually increase revenue.

At our agency (MILEDEVS), we've tested these patterns across 15+ stores over the past two years. Every trick below comes from a real A/B test or a measurable before-and-after on a production storefront. No theory — just code you can paste into your theme and adapt.


1. Dynamic Product Badges That React to Real Data

Static "SALE" badges are everywhere. They're also easy to ignore. A badge that says "32% OFF — ends Thursday" converts better because it carries specific, time-sensitive information.

{% comment %} snippets/dynamic-badge.liquid {% endcomment %}

{% if product.compare_at_price > product.price %}
  {% assign discount = product.compare_at_price | minus: product.price %}
  {% assign percent = discount | times: 100.0 | divided_by: product.compare_at_price | round %}

  <span class="badge badge--sale" aria-label="On sale">
    {{ percent }}% OFF
  </span>

{% elsif product.created_at | date: "%s" | plus: 604800 > "now" | date: "%s" %}
  <span class="badge badge--new" aria-label="New arrival">
    NEW
  </span>

{% elsif product.metafields.custom.bestseller == true %}
  <span class="badge badge--best" aria-label="Bestseller">
    BESTSELLER
  </span>
{% endif %}
Enter fullscreen mode Exit fullscreen mode

Why it works: The badge hierarchy prevents stacking (sale takes priority over "new," which takes priority over "bestseller"). The percentage is calculated from real price data, so it's always accurate. The 604800-second window (7 days) means "NEW" disappears automatically — no manual cleanup.

Result on a client store: Switching from a static "SALE" image to this dynamic badge increased click-through from collection pages by 11%.


2. Stock Urgency Without Lying to Customers

Fake scarcity ("Only 2 left!!" on every product) erodes trust. Real inventory-based urgency is different — it's honest, and it works.

{% comment %} snippets/stock-urgency.liquid {% endcomment %}

{% assign inventory_total = 0 %}
{% for variant in product.variants %}
  {% if variant.inventory_management == 'shopify' %}
    {% assign inventory_total = inventory_total | plus: variant.inventory_quantity %}
  {% endif %}
{% endfor %}

{% if inventory_total > 0 and inventory_total <= 5 %}
  <div class="stock-urgency" role="status">
    <svg class="stock-urgency__icon" aria-hidden="true" width="14" height="14">
      <circle cx="7" cy="7" r="5" fill="#e53e3e" />
    </svg>
    <span>Only {{ inventory_total }} left in stock</span>
  </div>
{% elsif inventory_total > 5 and inventory_total <= 20 %}
  <div class="stock-indicator" role="status">
    <svg class="stock-indicator__icon" aria-hidden="true" width="14" height="14">
      <circle cx="7" cy="7" r="5" fill="#38a169" />
    </svg>
    <span>In stock</span>
  </div>
{% endif %}
Enter fullscreen mode Exit fullscreen mode

The key detail: We only show a count when it's genuinely low (5 or fewer). Above that, we show a calm green "In stock" dot. If you show "Only 47 left" nobody cares — that's not urgency, that's inventory management leaking into your UI.


3. Smart Breadcrumbs That Reflect the Customer's Path

Default Shopify breadcrumbs show the product's primary collection. But if a customer arrived via a different collection, the breadcrumb should reflect their journey, not your taxonomy.

{% comment %} snippets/smart-breadcrumbs.liquid {% endcomment %}

<nav class="breadcrumb" aria-label="Breadcrumb">
  <ol itemscope itemtype="https://schema.org/BreadcrumbList">
    <li itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem">
      <a href="/" itemprop="item"><span itemprop="name">Home</span></a>
      <meta itemprop="position" content="1" />
    </li>

    {% if collection %}
      {% assign crumb_collection = collection %}
    {% elsif product.collections.size > 0 %}
      {% assign referrer = request.host | append: request.path %}
      {% assign crumb_collection = product.collections | first %}

      {% for col in product.collections %}
        {% if request.page_url contains col.handle %}
          {% assign crumb_collection = col %}
          {% break %}
        {% endif %}
      {% endfor %}
    {% endif %}

    {% if crumb_collection %}
      <li itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem">
        <a href="{{ crumb_collection.url }}" itemprop="item">
          <span itemprop="name">{{ crumb_collection.title }}</span>
        </a>
        <meta itemprop="position" content="2" />
      </li>
    {% endif %}

    {% if product %}
      <li itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem">
        <span itemprop="name" aria-current="page">{{ product.title }}</span>
        <meta itemprop="position" content="3" />
      </li>
    {% endif %}
  </ol>
</nav>
Enter fullscreen mode Exit fullscreen mode

What's happening: The snippet checks if a collection object is already in scope (meaning the customer navigated from a collection page). If not, it falls back to the product's first collection. The Schema.org markup is included because breadcrumbs are one of the easiest structured data wins for SEO.


4. Lazy-Loaded Sections for Below-the-Fold Content

Shopify sections render server-side, which means every section on your page adds to Time to First Byte. For content below the fold you can defer rendering until the user scrolls.

{% comment %} sections/lazy-testimonials.liquid {% endcomment %}

<div class="lazy-section" data-lazy-section="testimonials" style="min-height: 400px;">
  <noscript>
    {% render 'testimonials-content' %}
  </noscript>
</div>

<script>
  (function() {
    const observer = new IntersectionObserver((entries) => {
      entries.forEach(entry => {
        if (!entry.isIntersecting) return;
        const el = entry.target;
        const url = window.location.pathname + '?sections=' + el.dataset.lazySection;
        fetch(url, { headers: { 'Accept': 'application/json' } })
          .then(r => r.json())
          .then(data => { const key = Object.keys(data)[0]; if (key) el.innerHTML = data[key]; })
          .catch(() => { const ns = el.querySelector('noscript'); if (ns) el.innerHTML = ns.textContent; });
        observer.unobserve(el);
      });
    }, { rootMargin: '200px' });
    document.querySelectorAll('[data-lazy-section]').forEach(s => observer.observe(s));
  })();
</script>
Enter fullscreen mode Exit fullscreen mode

We've written more about this technique in our Shopify store optimization guide.


5. Conditional Free Shipping Bar With Real Cart Data

A free shipping bar should show how far the customer is from the threshold, update live when items are added, and disappear once the threshold is met.

{% assign threshold_cents = settings.free_shipping_threshold | times: 100 %}
{% assign cart_total = cart.total_price %}
{% assign remaining = threshold_cents | minus: cart_total %}

{% if threshold_cents > 0 %}
  <div class="shipping-bar" data-shipping-bar data-threshold="{{ threshold_cents }}" role="status" aria-live="polite">
    {% if remaining > 0 %}
      {% assign progress = cart_total | times: 100 | divided_by: threshold_cents %}
      <div class="shipping-bar__track">
        <div class="shipping-bar__fill" style="width: {{ progress }}%"></div>
      </div>
      <p>Add <strong>{{ remaining | money }}</strong> more for free shipping!</p>
    {% else %}
      <p class="shipping-bar__text--success">You've unlocked free shipping!</p>
    {% endif %}
  </div>
{% endif %}
Enter fullscreen mode Exit fullscreen mode

Wrapping Up

None of these tricks require an app install. They're pure Liquid and vanilla JavaScript.

If you want to go deeper, we publish technical breakdowns on our blog. And if you need a team to implement these optimizations, that's what we do at MILEDEVS.

The best optimization is the one you actually ship. Pick one, implement it today, measure for a week, and iterate.

Top comments (0)