There’s a way to lazy load images natively with hugo, by creating the layouts/_default/_markup/render-image.html file and putting the following code in 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 -}}
But this doesn’t seem to work, at least for me. I used the following code in a .md file:
![](/img/whatever.png#big)
I set the image size via css:
img[src*="#big"] {
width:480px;
}
And now when I added the lazy image code, I get an error while rendering the site:
Error: Error building site: “/blog/content/post/whatever.md:4:125”: execute of template failed: template: _default/_markup/render-image.html:4:125: executing “_default/_markup/render-image.html” at <imageConfig (printf “static/%s” .Destination)>: error calling imageConfig: open /blog/static/img/whatever.png#big: no such file or directory
So basically it thinks that the #big is a part of the filename. Is there anything I can do about it? Or maybe is there another way to lazy load the images?
One solution is to use split and remove the fragment before checking whether the image file is a resource. Can’t be certain because we cannot know the exact code of your render-image.html file.
Feel free to try the following, where I take the fragment and create a class. Might not be an ideal solution, but then again attribute selectors are not either. See the first-line comment on where to place the code.
In the following solution, I am both resizing the image to be loaded, and generating a low resolution placeholder to use as the img element’s background before the image lazy-loads. The placeholder is embedded in the HTML code in base64.
<!-- layouts/partials/render-image.html -->
<!-- get image URL from Markdown tag -->
{{- $src := ( .Destination | safeURL ) -}}
<!-- split # fragment and keep clean URL -->
{{- $fragments := ( split $src "#" ) -}}
{{- $src = index ($fragments ) 0 -}}
<!-- get actual filename -->
{{- $src = path.Base $src -}}
<!-- check if it exists as a page resource -->
{{- with ($.Page.Resources.ByType "image").GetMatch ( printf "**%s" $src ) -}}
<!-- resize if wider than 800px -->
{{- $resized := cond (lt .Width "800") . ( .Resize "800x" ) -}}
<!-- if a JPEG (certain to be opaque) generate a low resolution placeholder to use as background -->
{{ $placeholder := "" }}
{{- if or (eq (path.Ext .) ".jpg") (eq (path.Ext .) ".jpeg") }}
{{ $placeholder = .Resize "48x q20 jpg Gaussian" }}
{{ end -}}
<img src="{{ $resized.RelPermalink }}"
width="{{ $resized.Width }}"
height="{{ $resized.Height }}"
{{ with $placeholder }}style="
background-image: url('data:image/jpg;base64,{{ .Content | base64Encode }}');
background-position: 50% 50%;
background-repeat: no-repeat;
background-size: cover;"{{ end }}
alt="{{ $.Text }}" {{ with $.Title }} title="{{ . }}" {{ end }}
{{ with index ($fragments ) 1 }}class="{{ . }}" {{ end }}
loading="lazy"
>
<!-- or otherwise simply load the URL -->
{{- else -}}
<img src="{{ .Destination | safeURL }}"
alt="{{ .Text }}" {{ with .Title }} title="{{ . }}" {{ end }}
{{ with index ($fragments ) 1 }}class="{{ . }}" {{ end }}
loading="lazy"
>
{{- end -}}
Keeping the hook code in a partial, let’s me re-use in other partials too. I am all ear if the wonderful people in these forums can point to a better practice
Oh I see, then it’s definitely the following missing to find a resource:
{{ with imageConfig ( printf "static/%s" .Destination ) }}
It also assumes that you keep your images in static/ rather than page bundles, but that may be your use case.
Here’s a tweak with input from my code. It is still not ideal, because if it fails to find a resource (e.g. you have misstyped an image) it may still cause an error. If I understand right, imageConfig is quite vocal when it comes to errors. Haven’t tested it, but it should work:
{{- $src := ( .Destination | safeURL ) -}}
{{- if strings.HasPrefix $src "http" -}}
<img loading="lazy" src="{{ $src }}" alt="{{ .Text }}" {{ with .Title}} title="{{ . }}"{{ end }} />
{{- else -}}
{{- $fragments := ( split $src "#" ) -}}
{{- $src = index ($fragments ) 0 -}}
<img loading="lazy" src="{{ $src }}" alt="{{ .Text }}" {{ with .Title}} title="{{ . }}"{{ end }} {{ with imageConfig ( printf "static/%s" $src ) }} width={{ .Width }} height="{{ .Height }}" {{ end }}{{ with index ($fragments ) 1 }} class="{{ . }}" {{ end }}>
{{- end -}}
Edit:
Whatever comes after the # will be used as a class. If you want multiple classes, you might want to use some URL decoding there. No need to self close the img element in HTML5 by the way.
@Heracles, it looks like, this code works, i.e. it lazy loads images when they are displayed on the screen. But they are in full size. So basically I have now something like the following:
If you are referring to my original code, it should work with images in bundles (src relative to each page) but it will not resize images in static (see image processing and global resources). Might add this in the future.
Setting the width and height of an image is a best practice (if possible) because it helps against elements in the page jumping around (known as content shift). If your CSS limits either dimension and causes a deformation, then set the other to auto to fix this. For example: