Native image lazy loading with Hugo

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?

There is a pretty big difference between the error message and the template code you posted, making it hard/impossible to guess.

But without the layouts/_default/_markup/render-image.html file, everything works well.

@morfikov Does it work without the #big fragment?

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/_default/_markup/render-image.html -->
{{ partial "render-image" . }}

and

<!-- 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 :slight_smile:

1 Like

I’m not sure, because I have lots of posts with that kind of images. But it should.

I pasted the whole file in the first post.

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.

1 Like

@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:

<img loading="lazy" src="http://localhost:1313/img/someimg.png" alt="" class="huge" width="1299" height="681">

I had to remove the width={{ .Width }} height="{{ .Height }}" part because the image was deformed when i changed its width via css.

But other than that, your code works really nice. :slight_smile:

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:

img {
    max-width: 600px;
    height: auto;
}

@Heracles, actually I was talking about the last version of the code (the simple one). But I have one question. When I look in the code, I get this:

<p><img loading="lazy" src="https://someurl.tld/img/someimg.jpg" alt="" class="big"></p>

There’s a huge space between alt="" and class . Is there a way to remove the spaces? It looks like it’s 4 spaces or a tab.

This topic was automatically closed 2 days after the last reply. New replies are no longer allowed.