Responsive and optimised images with Hugo

Flexible content editor friendly image slideshows

I manage a handful of Hugo (it’s a great framework to build really good websites with) projects, and although they work really well, there is always something more you can do to improve the experience on the front end of a site.

A combination Forestry and Hugo on non-dynamic content editable sites is really powerful without the overheads of a database CMS (which have their place). If set up right, content editors can be spared the tyranny of confusing shortcodes cluttering seemingly tidy content areas with editing tools with an ease of use familiar in a CMS such as Craft.

You don’t need Forestry in this example, but it does provide a good content model. The example I want to share is more to do with Hugo templates.

Hugo has many tricks built in, and it’s this appeal without resorting to npm install even-more-web-jenga that I like. Hugo has Image Processing built in, and although it’s not too difficult to set up in some instances, I was soon running into maze, as every example I came across dealt with images with shortcodes and content areas in .md files. Which is fine, but I’m pulling my images from Front Matter, and with an extra twist, a range of images built with Forestry Blocks feature.

Selecting one image reference from the Front Matter of a page was fine - just, but I needed the range of images to appear in a slider and aim to:

  • Create multiple image instances from the original optimised for multiple size screens and utilise scrset
  • Make sure each image has alt text
  • Flexibility to be pulled into any template sitewide

First up, in any page the front matter might look like this, from which I wanted to build a range directly into the template, it’s fairly straightforward:

[[image_gallery]]
gallery_image = "/images/image1.jpg"
gallery_image_alt_text = "Image 1 alt"

[[image_gallery]]
gallery_image = "/images/image2.png"
gallery_image_alt_text = "Image 2 alt"

I want the images to appear on the single.html page but also elsewhere (like the index.html) so creating a partial makes most sense in /partials called gallery.html and call it with:

{{ partial "gallery.html" . }}

… so far, so good.

This is where things get interesting.

Next, the images folder needs an index.md file that Hugo can reference and match images with Page Bundles - best explained by Laura Kalbag, but all that needs to be in it is:

---
headless: true
---

At this point I can start getting inside the gallery range:

{{ range .Params.image_gallery }}

I found a variable for the alt text was helpful so this can be used more easily within a with function.

{{ $alttext := .gallery_image_alt_text }}

To get the next line right is where potentially days of time could be lost. Matching the image references to the images in the images folder. I still don’t fully understand the process here, but not to worry, Hugo can match all the references with that index.md that was created.

{{ $imageResource := ($.Site.GetPage "images").Resources.GetMatch (strings.TrimPrefix "/images/" . ) }}

Now that there are images to process, Hugo can work with these. I’ve set 4 different images, but this can be set to anything that suits what sizes and ratios for a project. At build, Hugo with produce all these images with their own permalinks, which can now be used.

Again, needs will change to the right html for the project as to what sizes are best, but because the original image is now a variable as $imageResource, the size of the new images can be set with their own variables, for example $resizedsm will reference the image that has been resized with a width of 640px. These new ones are the ones that are the images that get displayed in the html.

{{ $resizedsm := $imageResource.Resize "640x" }}
{{ $resizedmd := $imageResource.Resize "768x" }}
{{ $resizedlg := $imageResource.Resize "1024x" }}
{{ $resizedxl := $imageResource.Resize "2048x" }}

<img src="{{ $resizedsm.RelPermalink }}" />

Finally, that alt text can be called with:

alt="{{ $alttext }}"

Putting it all together

My partial looks something like this which then can be wrapped up in any flavour of carousel or page:

{{ if .Params.image_gallery }}
{{ range .Params.image_gallery }}

{{ $alttext := .gallery_image_alt_text }}

{{ with .gallery_image }}
{{ $imageResource := ($.Site.GetPage "images").Resources.GetMatch (strings.TrimPrefix "/images/" . ) }}

{{ $resizedsm := $imageResource.Resize "640x" }}
{{ $resizedmd := $imageResource.Resize "768x" }}
{{ $resizedlg := $imageResource.Resize "1024x" }}
{{ $resizedxl := $imageResource.Resize "2048x" }}

<picture>
  <source media="(min-width: 1280px)" srcset="{{ $resizedlg.RelPermalink }} 1x,
    {{ $resizedxl.RelPermalink }} 2x,
    {{ $resizedxl.RelPermalink }} 3x">

  <source media="(min-width: 1024px)" srcset="{{ $resizedlg.RelPermalink }} 1x,
    {{ $resizedxl.RelPermalink }} 2x,
    {{ $resizedxl.RelPermalink }} 3x">

  <source media="(min-width: 768px)" srcset="{{ $resizedmd.RelPermalink }} 1x,
    {{ $resizedlg.RelPermalink }} 2x,
    {{ $resizedxl.RelPermalink }} 3x">

  <source media="(min-width: 640px)" srcset="{{ $resizedsm.RelPermalink }} 1x,
    {{ $resizedmd.RelPermalink }} 2x,
    {{ $resizedlg.RelPermalink }} 3x">

  <img alt="{{ $alttext }}" src="{{ $resizedmd.RelPermalink }}" />
</picture>

{{ end }}
{{ end }}
{{ end }}

References and thanks to: