Beta website

Native image lazy loading with Hugo

Finally, natively lazy loading images has arrived and it’s going to be easier to set up than you may have thought.

Meet the loading attribute.

Image comparing two img links, one lazy loaded one not

The new loading attribute tells browsers what priority it should load images with, or if it should even load them at all. Initially released in 2019 this attribute is still pretty new but already has support in Edge, Firefox and Google Chrome.

As of today it has three options (taken from this web.dev article):

  • auto: Default lazy-loading behaviour, which is the same as not including the attribute
  • lazy: Defer loading of the resource until it reaches a calculated distance from the viewport
  • eager: Load the resource immediately, regardless of where it’s located on the page

Below we’ll go into one of the negatives of lazily loading images and cover how to implement this new feature using Hugo.

Page repaint

One major downside of lazy loading is it can cause the page to repaint or shift around, sometimes known as jankiness. You’ve probably seen this before when a webpage sticks an ad in the middle of an article causing everything to suddenly shift down to accomodate it.

The easiest way to avoid this is to tell browsers what size the images are within your page so that it knows exactly how much space it’s going to take once loaded. You do this by adding the image height and width in pixels as an attribute.

This video from Mozilla covers it in more detail.

Lazy loading with Hugo

To lazy load images within Hugo two things are required:

  1. Add the loading="lazy" attribute
  2. Where possible add the image size to prevent repainting

You can’t do this via JavaScript as the browser checks for the attribute when the page initially loads.

Adding the loading attribute

Hugo has a piece of functionality called Render Hooks which allows you to write templates for specific markdown elements like links or images. The image hook will let you add the loading="lazy" attribute while keeping all your content as plain markdown.

Render hooks are really easy to get going since they’re just like any other Hugo template, you just need to create files under the _markup directory in the layouts folder.

Adding image size

Adding image sizes is slightly more difficult but can also be done completely within Hugo itself using the imageConfig function. This function exposes image sizes as variables that can then be inserted into the render hook template to prevent the page from repainting.

You wont be able to add image sizes for any remote images as the function only works on ones it can read directly off the file system. Something to keep in mind if you have a large amount of remote images on your site.

Throwing it all together

  1. Create a new file in layouts/_default/_markup/ called render-image.html
  2. Put the below code into it
    {{- if strings.HasPrefix .Destination "http" -}}
      <img loading="lazy" src="{{ .Destination | safeURL }}" alt="{{ .Text }}" {{ with .Title}} title="{{ . }}"{{ end }} />
    {{- else -}}
      <img loading="lazy" src="{{ .Destination | safeURL }}" alt="{{ .Text }}" {{ with .Title}} title="{{ . }}"{{ end }} {{ with imageConfig ( printf "static/%s" .Destination ) }} width={{ .Width }} height="{{ .Height }}" {{ end }} />
    {{- end -}}
    
  3. Success!

That’s it! Now your images will lazily load in the background.

Rejoice as you save precious bandwidth for images that are never displayed.