Announcing new Liquid features for better web performance

By admin
We’ve added

two
CARDINAL

exciting new features to

the Liquid API
PRODUCT

to aid in web performance:

Default lazy loading for the image_tag for sections further down the page

for the for sections further down the page New section properties section.index ,

section.index0
TIME

, and section.location for more fine-tuning of image lazy loading as well as async CSS loading.

We created these new section properties specifically to help with

three
CARDINAL

web performance issues that occur due to layout position:

Lazy loading images above the fold

Asynchronous loading of CSS needed for elements above the fold

Not prioritizing the fetch of the the Largest Contentful Paint (LCP) image

If you’d like to better understand the concepts of those issues, read my last article, How layout position impacts

three
CARDINAL

big web performance levers. In this article, we’ll cover:

New default lazy loading for

Liquid image_tag Jump
PRODUCT

to heading #

Before, the Liquid image_tag did not apply any logic for any default settings for the img loading attribute. Now, any image_tag past the

first
ORDINAL


three
CARDINAL

sections of a template will automatically set loading=”lazy” if the loading attribute is not already set (

docs
PERSON

). This specific logic may change in the future as we look at real user data and adjust the default value to maximize performance across all Shopify storefronts. The idea is to not lazy load images above the fold which causes a poor user experience and a slower

LCP
ORG

.

To take advantage of this new feature, use the image_tag filter for your images and do not set the loading attribute:

{{ section.settings.image | image_url: width:

300
CARDINAL

| image_tag }}

< img

src = " //cdn.shopify…/files/dog.jpg?width=300 "

width = "

300
CARDINAL

"

height = "

393
CARDINAL

" />

< img

src = " //cdn.shopify…/files/dog.jpg?width=300 "

loading = " lazy "

width = "

300
CARDINAL

"

height = "

393
CARDINAL

" />

This is a great way to simplify your theme code if your sections are generally large enough that

no more than 3
CARDINAL

inside of your template or content_for_layout would be “above the fold”. If, however, you need more fine-tuning or if you don’t want to be subject to changes in our default algorithm, we’ve also released some new section properties…

Each browser has a different algorithm for when to load lazy images, which can be based on the effective connection speed and type of image, for example. You can see

Chrome
ORG

’s thresholds in their source code. Learn more about how native lazy loading works in Browser-level image lazy loading for the web.


New Liquid
LOC

section properties Jump to heading #

In our previous article, we showed that

82.4%
PERCENT

of Shopify pages are lazy loading their

LCP
ORG

image.

One
CARDINAL

reason why this antipattern happens is because before now, theme devs had no contextual information about where a section was located when rendering.

Today
DATE

, that is different. We have

three
CARDINAL

new properties for you (

docs
PERSON

):

section.index – the

1
CARDINAL

-based index of the section within its contextual location

– the

1
CARDINAL

-based index of the section within its contextual location section.index0 – the

0
CARDINAL

-based index of the section within its contextual location

– the

0
CARDINAL

-based index of the section within its contextual location section.location – the section’s location (can also be thought of as the section’s context or scope)

What is section.location ? Jump to heading #

Before we cover the index properties, it’s best to understand what we mean by the location . Sections can be rendered in many different locations. Most sections are rendered in the template, but many are also rendered within section groups:

A common layout will include a header section group, the template (content for layout), and a footer section group.

When the section is located within the template, its section.location will be template . When the section is located within a section group, its section.location will be the section group type, which can be header , footer , aside , and custom.<name> .


Templates
ORG

and section groups cover most section locations, but we have

one
CARDINAL

final location type which is static , for a statically rendered section:

Static sections are outside of templates and section groups.

If you have a legacy theme that still uses content_for_index , the location will be content_for_index .

What are section.index and

section.index0
TIME

? Jump to heading #

Both index and

index0
PERSON

are integers representing the index, or order, of the section within its location. For consistency with previous Liquid features, index is the

1
CARDINAL

-based index while

index0
PERSON

is the

0
CARDINAL

-based index.

Stated another way, the index restarts within each location, or context. For example, say we have a page with the following layout:

Header group Announcement banner section Nav bar section

A static section

Template Image with text section Another image with text section Featured articles section


Footer
PERSON

group

Footer section

ORG

The location and indices would be the following:

Section location index

index0 Announcement
PERSON

banner header

1
CARDINAL

0 Nav bar header

2 1
CARDINAL

Static section static nil nil Image with text template

1 0
CARDINAL

Another image with text template

2 1
CARDINAL

Featured articles template

3 2
CARDINAL

Footer footer

1 0
CARDINAL

Warning! Both index and

index0
PERSON

are nil in the following cases: For statically rendered sections

For sections rendered using

the Section Rendering API

LAW

When rendering the Online Store Editor

The Online Store Editor is optimized for fast rendering by re-rendering only the section that was updated. This means that if we provided the index , it would not be consistent. Thus, both the index and

index0
PERSON

are forced to be nil when rendering in the Online Store Editor. These new features should be used only for non-visual reasons like optimizing loading speed for real users.

Example use cases and code for using the new section properties Jump to heading #

Before we begin, remember that we only need to apply lazy loading for images either below the fold or which are not visible until an interaction (e.g., opening a menu). If an image will always be visible above or even near the fold, it’s safest to eagerly load that image. For example, your main product image should probably be eager loaded.

The use case for selectively applying eager or lazy loading is for sections that can be reused across templates and section groups. The indices will be nil for all other sections.

Lazy load an image based on section.index

Jump
PERSON

to heading #

Remember that the new default behavior for the image_tag is to set loading to lazy for images after the

first
ORDINAL


three
CARDINAL

sections, currently. This example manually sets it to lazy after the

first
ORDINAL


two
CARDINAL

sections to show how you would override that behavior:

{%- liquid

if section.index >

2
CARDINAL

assign loading = "lazy"

else

assign loading = "eager"

endif

-%}

{{

section.settings.image

| image_url: width: 1080

| image_tag: loading: loading

}}

Asynchronously load CSS based on section.index

Jump
PERSON

to heading #

Another problem we see on Shopify storefronts is layout shifts due to late arriving CSS by using the async CSS hack. If you still want to use that hack, but limit it to sections further down the page so that it does not impact

Cumulative Layout
ORG

Shift (

CLS
ORG

), then you can similarly use section.index to selectively apply it:

{% if section.index >

2 %
PERCENT

}

< link

rel = " stylesheet "

href = " {{ ‘

section-image-banner.css ‘ |
ORG

asset_url }} "

media = " print "

onload = " this . media = ‘all’ " >

< noscript >

{{ ‘

section-image-banner.css’ | asset_url |
ORG

stylesheet_tag }}

</ noscript >

{% else %}

{{ ‘

section-image-banner.css’
ORG

| asset_url | stylesheet_tag }}

{% endif %}

Considerations for card lists Jump to heading #

For sections that list multiple items with pictures using a forloop , you will need a more complex check for applying lazy loading.

In the following image, the banner image will likely be the

LCP
ORG

element, but we would also want at least the

first
ORDINAL


three
CARDINAL

items in the featured collection to be eager loaded.

For loops of images, use the forloop.index in addtion to the section.index to optimize the lazy loading pattern.

Our forloop will check both section.index and

forloop.index
ORG

to determine whether to set loading to eager or lazy. If the theme supports changing how many columns are shown on desktop, you maybe want to take that number into consideration too. In the above example, If the section.index was 0, I would set the

first
ORDINAL


6
CARDINAL

-9 images to eager. If it was

1
CARDINAL

, I would set the

first
ORDINAL

3-6 to eager. I might wait until a section.index of

3
CARDINAL

to start setting them all to lazy, since partial images might still be visible.

You don’t have to be perfect. You can test simplifying this logic, for example maybe skipping the middle step. I would err on the side of less lazy loading. Then use a tool like WebPageTest to see if there is a significant difference in the performance metrics.

Setting fetchpriority for large image sections likely to be an LCP Jump to heading #

Warning: Fetch priority is a powerful feature but can easily be misused causing worse performance. Only use this example after extensive testing.

Certain sections like image banners have a very high likelihood of being the

LCP
ORG

element because their image is so large. You could reason that if the banner image section is the

first
ORDINAL

or maybe even

second
ORDINAL

section in a template, it will likely be the

LCP
ORG

element. In this case, setting fetchpriority="high" may bump up our

LCP
ORG

speed even faster.

{%- liquid

assign loading = "eager"

assign fetchpriority = "auto"

if section.index ==

1
CARDINAL

assign fetchpriority = "high"

elsif section.index >

2
CARDINAL

assign loading = "lazy"

endif

-%}

{{

section.settings.image

| image_url: width: 1080

| image_tag:

loading: loading,

fetchpriority: fetchpriority

}}

You can simplify your code to this if you take advantage of default lazy loading:

{%- liquid

assign fetchpriority = "auto"

if section.index ==

1
CARDINAL

assign fetchpriority = "high"

endif

-%}

{{

section.settings.image

| image_url: width: 1080

| image_tag: fetchpriority: fetchpriority

}}

We’re happy to announce these new

Liquid
PRODUCT

features as they should make it easier to develop faster themes. Faster themes lead to faster stores and higher conversions. We’d love to hear your feedback and success stories.