Working with Hugo, one of the best-known and fastest static site generators is excellent. As a (primarily) backend developer, I’m not always comfortable with site builders as I find them clunky. Also, I depend on well-designed and beautiful themes created by talented designers and front-end specialists. They’ve mostly worked out all the stuff I find ‘finicky’, so I don’t have to.

However, there are elements I can do to optimize my website for speed and load. In this post, I will look at converting my images so that they are responsive using Hugo’s image processing capabilities. In addition I will be converting the images from jpg to webp format. Webp formats are said to be 25-34% smaller than a comparable jpg file.

Photographers like big images

The worst thing about visiting websites built a decade or more ago is the small images, which need to be more responsive and look tiny on my big old screen. The quality could be better, and I can’t see the detail. It’s pointless having a great camera with megapixels to die for and yet can’t show the images with high enough quality on screen. The flip side is loading up 10 MB images on a mobile device over a 3G or slower connection; even the best phones will need help.

Hugo provides image processing features that enable developers to use images optimized for the modern web. In addition it also allows you to use the excellent HTML5 features to seamlessly deliver pictures for various-sized devices. Let’s see how you can do this.

Image formats

I’m going to concentrate on the webp format.

What we will use

  • Hugo figure shortcode template
  • Hugo partials
  • Hugo image processing
  • picture exif data

The following code shows the default internal figure template that Hugo provides.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<figure{{ with .Get "class" }} class="{{ . }}"{{ end }}>
    {{- if .Get "link" -}}
        <a href="{{ .Get "link" }}"{{ with .Get "target" }} target="{{ . }}"{{ end }}{{ with .Get "rel" }} rel="{{ . }}"{{ end }}>
    {{- end -}}
    <img src="{{ .Get "src" }}"
         {{- if or (.Get "alt") (.Get "caption") }}
         alt="{{ with .Get "alt" }}{{ . }}{{ else }}{{ .Get "caption" | markdownify| plainify }}{{ end }}"
         {{- end -}}
         {{- with .Get "width" }} width="{{ . }}"{{ end -}}
         {{- with .Get "height" }} height="{{ . }}"{{ end -}}
         {{- with .Get "loading" }} loading="{{ . }}"{{ end -}}
    /><!-- Closing img tag -->
    {{- if .Get "link" }}</a>{{ end -}}
    {{- if or (or (.Get "title") (.Get "caption")) (.Get "attr") -}}
        <figcaption>
            {{ with (.Get "title") -}}
                <h4>{{ . }}</h4>
            {{- end -}}
            {{- if or (.Get "caption") (.Get "attr") -}}<p>
                {{- .Get "caption" | markdownify -}}
                {{- with .Get "attrlink" }}
                    <a href="{{ . }}">
                {{- end -}}
                {{- .Get "attr" | markdownify -}}
                {{- if .Get "attrlink" }}</a>{{ end }}</p>
            {{- end }}
        </figcaption>
    {{- end }}
</figure>

Using this code as a starting point, we will amend it to produce an image sized for our media breakpoints provided by our CSS.

We will be making use of the HTML <picture> tag

1. Extract variables

First, we will extract some variables so we can use and pass them into different files that we need to create. In the above template, every .Get <variable> is a parameter passed to the shortcode. So we will assign them as follows.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
{{ $alt := .Get "alt" }}
{{ $attr := .Get "attr" }}
{{ $attrlink := .Get "attrlink" }}
{{ $caption := .Get "caption" }}
{{ $class := .Get "class" }}
{{ $height := .Get "height" }}
{{ $link := .Get "link" }}
{{ $rel := .Get "rel" }}
{{ $src := .Get "src" }}
{{ $style := .Get "style" }}
{{ $target := .Get "target" }}
{{ $title := .Get "title" }}
{{ $width := .Get "width" }}

We will do additional treatments with the $alt variable since we often add caption data to the photo’s EXIF data during post-production. We will privilege the caption/alt data passed to the shortcode and only use the EXIF data caption if no caption has been explicly passed to the shortcode.

this is the caption

To get the EXIF data, we first have to extract it from the image resource.

1
{{ with .Exif}}{{ with .Tags}}{{.ImageDescription}}{{end }}{{end}}

Now, this code by itself won’t give you what you need if you’re running it outside of the context in which it’s used, but we can set it in a partial, and ask that partial to return the data to us.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
// layouts/partials/exif/caption.html

{{/* fallback */}}
{{ $caption := ""}}
{{ with .Exif}}
  {{ with .Tags}}
    {{ $caption = .ImageDescription }}
  {{end }}
{{ end }}
{{ return $caption}}

Using this partial, we can reuse the same code elsewhere, in the picture and the figure caption.

2. Create picture partial

The variables we’re interested in using for the picture are

  • $src
  • $altTitle
  • $class
  • $lazy

We will pass these as parameters to the partial using the dict function.

We will replace

1
2
3
4
5
6
7
8
<img src="{{ .Get "src" }}"
  {{- if or (.Get "alt") (.Get "caption") }}
  alt="{{ with .Get "alt" }}{{ . }}{{ else }}{{ .Get "caption" | markdownify| plainify }}{{ end }}"
  {{- end -}}
  {{- with .Get "width" }} width="{{ . }}"{{ end -}}
  {{- with .Get "height" }} height="{{ . }}"{{ end -}}
  {{- with .Get "loading" }} loading="{{ . }}"{{ end -}}
  >

With a call to this partial, which we will create.

1
{{ partial "_images/picture" (dict "src" . "class" $class "alt" $alt "lazy" "true") }}

The partial looks like this

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// partial/_images/picture.html
//First we assign the inputs to some local variables 
{{ $original_image := .src }}
{{ $class := .class }}
{{ $lazy := .lazy }}

//This is our partial to extract the caption from our image EXIF data 
{{- $caption := .alt | default (partial "_images/exif/caption" $original_image) -}}

//We store the original image width and height to use later
{{ $w := float $original_image.Width }}
{{ $h := float $original_image.Height}}

//These are the media query min widths our CSS can handle
{{ $media_query_widths := slice 1400 1200 992 768 576 480 350}}

<picture>
  //Loop through these and create a version of the image that matches
  {{ range $media_query_widths}}

  {{$nw := .}}
  
  //First, multiply the original height by the ratio to get the new resized height we need 
  {{ $nh := mul (div $h $w) $nw | int }}

  //We use the new width and new height to create the parameters for the resize function
  {{- $params_webp := printf "%dx%d webp q100" $nw $nh -}}
  {{- $image_webp := $original_image.Resize $params_webp -}}
 
  <source media="(min-width:{{$nw}}px)" srcset="{{ $image_webp.Permalink }}" type="image/webp">
  {{ end }}
  <img {{ with $lazy}} loading="lazy" class="lazy" data-src="{{ $original_image.RelPermalink }}" {{else}}
    src="{{ $original_image.RelPermalink }}" {{ end }} alt="{{ $caption }}">
</picture>

The above code will produce the desired images and set them as alternative source media from which the browser will choose the most appropriate size.

Conclusion

Using the information in this tutorial, you can use next-gen formats and optimize your images for different devices using Hugo’s image processing resources.

If you liked this post, please share it with others: