Chris Wiegman

Better Responsive Images with Hugo

I’ve really enjoyed getting back to Hugo for this site, but it does take a bit more work to get some things right. For me one of those things is images.

What I wanted to do was load the appropriate sized image for the appropriate sized screen. Out of the box Hugo will give you a standard img tag without any handling of the image, but you can do better.

Here’s how you can generate appropriate sized images for your Hugo site that will respond to your screen size.

First, if you’re loading your images from the static folder in Hugo move them instead to assets. This is because Hugo’s assets folder allows for Hugo Pipes which is Hugo’s way of processing files before they’re published to the web.

Next we’ll want to add our image sizes to our Hugo config file (I use hugo.json so you might need to adjust the format for your site). We’ll add images sizes and the image size string to our params (the latter just to make it all a bit easier).

{
  "params": {
    "imageSizes": [
      850,
      710,
      300
    ],
    "imageSizeString": "(max-width: 850px) 850w, (max-width: 710px) 710w, (max-width: 300px) 300w"
  }

In the above example you can see I’m generating 3 image sizes that will be used, 300px wide, 710px wide and 850px wide. If I was to adjust my site design I could simply modify the image size settings and it would regenerate the new sizes.

Next we need to edit Hugo’s default image markup. We’ll do that by creating the following file:

layouts/_default/_markup/render-image.html

Add to it the following:

 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
35
36
37
{{- $image_file := .Destination -}}
{{- $image := resources.Get $image_file -}}
{{- $imgSrc := "" -}}
{{- $imgSrcSet := slice -}}
{{- with $widths := site.Params.imageSizes -}}
{{- range $widths -}}
{{- $srcUrl := (printf "%gx" . | $image.Resize).RelPermalink -}}
{{- if eq $imgSrc "" -}}
{{- $imgSrc = $srcUrl -}}
{{- end -}}
{{- $imgSrcSet = $imgSrcSet | append (printf "%s %gw" $srcUrl .) -}}
{{- end -}}
{{- $imgSrcSet = (delimit $imgSrcSet ",") -}}
{{- end -}}

{{- if .IsBlock -}}
<figure>
    {{- if gt $image.Width 850 -}}
    <img {{ with .Title }} class="with-caption" {{ end }} width="{{ $image.Width }}" height="{{ $image.Height }}"
        src="{{ $image.RelPermalink }}" alt="{{ . }}" decoding="async" fetchpriority="high" srcset="{{ $imgSrcSet }}"
        sizes="{{ site.Params.imageSizeString }}" />
    {{- else -}}
    <img{{ with .Title }} class="with-caption" {{ end }} width="{{ $image.Width }}" height="{{ $image.Height }}"
        src="{{ $image.RelPermalink }}" alt="{{ . }}" decoding="async" fetchpriority="high" />
    {{- end -}}
    {{- with .Title }}<figcaption>{{ . }}</figcaption>{{- end -}}
</figure>
{{- else -}}
{{- if gt $image.Width 850 -}}
<img width="{{ $image.Width }}" height="{{ $image.Height }}" src="{{ $image.RelPermalink }}" alt="{{ . }}"
    decoding="async" fetchpriority="high" srcset="{{ $imgSrcSet }}" sizes="{{ site.Params.imageSizeString }}" {{- with
    .Title }} title="{{ . }}" {{ end -}} />
{{- else -}}
<img width="{{ $image.Width }}" height="{{ $image.Height }}" src="{{ $image.RelPermalink }}" alt="{{ . }}"
    decoding="async" fetchpriority="high" {{- with .Title }} title="{{ . }}" {{ end -}} />
{{- end -}}
{{- end -}}

Lines 1-14 will grab the image and generate all the individual sizes.

The rest will output the html for the image with a caveat. Notice likes 60 and 71. These will only output the srcset if the original image was greater than 850px wide, otherwise they’ll just use the original image. You might want to adjust that for your own site.

Now build your site and check the output, you should see various new image files in the img tag corresponding to the sizes you chose. If your image is a block it will even output a caption if a title had been provided in the markdown.