Skip to content
Log in

Cache - Liquid Tag Reference

On this page

The cache block tag stores rendered output in the server-side cache so it can be reused without re-processing. Wrap expensive queries, loops, or external calls in {% cache %}...{% endcache %} to cut page render time.

Every cache block needs a cache key as its first argument. A block with no key raises an error when the template renders.

Syntax

```liquid

{% cache “", items: [], expires_in: %} {% endcache %} ```

Property Value
Tag Name cache
Type Block tag
Source Hydrofoil (core)

Parameters

Parameter Type Description
Cache key String Required, passed as the first argument with no parameter name. A short name for the block, or a variable holding one. Rendering fails if it is missing or empty.
items Array The objects the block’s output depends on. Each one is added to the cache key so every product, customer, or cart gets its own cached copy.
expires_in Number How long the cached output lives, in seconds. Defaults to 60 seconds.
race_condition_ttl Number How long, in seconds, the expired copy keeps being served while a new one is generated. Defaults to 5 seconds. When expires_in is 5 seconds or less, this becomes half of expires_in.
force Boolean Set true to skip the existing entry and rewrite it. Useful while developing.
skip_nil Boolean Set true to leave the cache untouched when the block produces nothing.

How the cache key is built

The final key combines the name you pass, the current store, and each entry in items. Two things follow from this:

  • Keys are isolated automatically. The same cache name in two different stores, or in two different Salesforce orgs, never collides. You do not need to add the store to the key yourself.
  • items scopes the key, it mostly does not watch for edits. Most objects contribute only their record ID, so passing product gives that product its own cached copy, but editing the product does not clear it. current_cart is the exception: it contributes a version that moves whenever the cart changes, so a block keyed on the cart refreshes as soon as the cart does.
  • Expiry is the only general way out. A cache block refreshes when expires_in elapses. There is no way to clear a single block on demand, so choose expires_in to match how quickly the content has to be correct.

Changing the Cache Version field on the Store record in Salesforce, which publishing a content change does for you, clears the store’s page cache. It does not reach inside cache blocks: those are keyed separately and keep serving their copy until it expires.

:::note Caching is bypassed while you preview an unpublished content change in Studio, so the editor always shows current content. A change that looks correct in the editor can still serve a cached copy on the live store until the block expires. See changes not showing on the website. :::

Examples

Cache a product card

Pass the product so each one is cached separately, and the store and customer so that price, availability, and customer-specific text stay correct:

```liquid

{%- cache “product”, items: [product, current_store, current_customer] -%}

{{ product.name }}

{{ product.pricing.price | money }} {%- render “products/product/add_to_cart”, product: product -%} {%- endcache -%} ```

Cache the header

The header shows the cart item count, so include current_cart. Without it, a customer’s own header would keep the count it had when the block was cached, and signed-out visitors, who share a single key, would see each other’s count:

```liquid

{%- cache “header”, items: [current_store, current_customer, current_cart] -%} {%- render “menu” -%} {{ current_cart.item_count }} {%- endcache -%} ```

Build the key from a variable, and hold it longer

Where a name alone is not specific enough, build the key first and pass the variable. Content that changes rarely can be held well beyond the default minute, so this related-products list is cached for ten minutes:

```liquid

{%- capture related_key %}related/{{ product.id }}{% endcapture -%} {%- cache related_key, items: [current_customer], expires_in: 600 -%} {%- for related in product.related_products -%} {%- render “products/card”, product: related -%} {%- endfor -%} {%- endcache -%} ```

Cache an external call

Wrap an api block so the storefront does not call the service on every render:

```liquid

{%- cache “forecast”, items: [current_store], expires_in: 900 -%} {% api url: ‘https://api.example.com/v1/forecast’, method: ‘GET’, async: false %} {% if response.status == 200 %}{{ response.body.summary }}{% endif %} {% endapi %} {%- endcache -%} ```

Additional recommendations

  • Always pass the objects the output depends on. The most common caching bug is omitting current_customer or current_cart from a block whose output differs per customer, which makes one customer’s rendered output visible to everyone until it expires.
  • Never cache a block containing a form tag. Forms include a hidden security token tied to the visitor’s session, so a cached copy hands the next visitor a token that fails validation on submit. The same applies to anything else built from session values.
  • Cache the expensive part, not the whole page. Small blocks around slow queries and external calls give most of the benefit and stay correct for longer.
  • Measure before and after. Wrap the block in a timer block to confirm it is worth caching.
  • Keep expiry short while developing, or pass force: true, so you are not debugging output that a cached copy is producing.

:::warning Prices, stock levels, and cart contents stay stale for as long as the cached copy lives. Keep expires_in short on anything a customer makes a purchase decision from, and always include current_customer in items so that customer-specific pricing, approvals, and membership discounts are not shared between visitors. :::

Was this article helpful?

Was this article helpful?