Real World Craft CMS

Image Transforms and Eager Loading

We add transforms to our images so they're the minimum needed size, and then improve performance by eager loading the images and transforms.

When we load in images dur­ing an ele­ment query and for-loop, like we are with the adven­ture images, we can run into an n+1 issue. That means that every loop pro­duces at least one addi­tion­al query to find the image for that ele­ment. This isn’t effi­cient and can cause per­for­mance issues. 

Because of that, we want to eager load our images and their trans­forms, so we grab every­thing we need upfront rather than delay­ing it until we’re in the loop for each element.

Also, in Craft 4, there are some changes to how eager­ly loaded ele­ments work. All eager-loaded ele­ments are returned as col­lec­tions instead of stan­dard data arrays. Because of this change, we don’t need to change our code for how we access the data that is returned from an eager­ly loaded element.

To do this, we use the with method to tell Craft to eager load any ele­ment han­dles we pass in. In this case, our field han­dle is images, so we’ll pass it in in square brack­ets and in sin­gle quotes.

{% set adventures = craft.entries  
    .section('adventures')  
    .limit(10)
    .with(['images'])  
    .all()
%}

We can dump and die the results of the adven­tures query to see that we have an eager­ly loaded ele­ment in the returned data.

Because of the changes in Craft 4 with how it returns eager­ly loaded ele­ments, it now sup­ports access­ing the array ele­ments via the one method as an alias to the first() method in Lar­avel Col­lec­tions. That means we don’t have to change our code where we out­put the image. Every­thing just works as it is.

<img  
        src="{{ adventure.images.one.url('adventureListing') }}"  
        alt=""  
        class="w-full h-full object-center object-cover group-hover:opacity-75"  
/>

One thing you’ll notice is that we are also retriev­ing an image trans­form as part of the code. That is some­thing that we can eager load, too, along­side our image. This will also pre­vent anoth­er n+1 issue that we might encounter.

This uses a sec­ond para­me­ter in the with method called withTranforms. We sur­round it with curly braces, and the prop­er­ty name is withTransforms and then we pass in an array of trans­form han­dles we want to eager load. In our case, we’re going to eager load the one trans­form we need.

            {% set adventures =
                craft.entries
                    .section('adventures')
                    .with([
                    ['images', {
                        withTransforms: ['adventuresListing']
                    }]
                ])
                    .limit(10)
                    .location(category ?? '')
                    .all() %}

While this has a min­i­mal impact right now because our site is small, using this best prac­tice is going to pay div­i­dends going for­ward as you add more data out­put to your templates.

Real World Craft CMS is made up of the following videos: