Rick Wysocki
July 20, 2023
In this tutorial, I’ll describe how to write conditional image layouts using Go and HTML for a Hugo site.
When I started designing Little Ghost, my Hugo theme, I wanted to conditionally insert featured images based on parameters called in a page’s front matter and the site configuration file. Hugo’s figure shortcode was an option, but I wanted something more automatic. After a bit of learning, I learned how easy this can be in Hugo.
There were a few criteria I wanted to meet for featured image layouts. Specifically, I wanted Hugo to:
Finally, I wanted this to be optional. If no front matter is declared, no HTML should be rendered for the image layout.
Here is the entire HTML and Go markup / code for this feature. I’ll explain each line below.
{{ if isset .Params "featured_image" }}
{{ $image := .Resources.GetMatch .Params.featured_image }}
<img
{{ with $image }}src="{{$image.RelPermalink }}"{{end}}
alt="{{ .Params.featured_alt }}"
/>
{{ end }}
So what is each line doing here?
First, we check whether a featured_image
is set in the front matter. If there isn’t everything else will be ignored:
{{ if isset .Params "featured_image" }}
Assuming we have a featured image, we want to get it as a page resource. This is important because, as described in the Hugo documentation, page resource files get a page-relative URL, which simplifies the front matter we need to write. Additionally, we’ll see later that getting our image as a page resource will let us make creative use of Hugo’s image-processing methods.
So, what we want to do is:
featured_image
.The next line accomplishes all three of these tasks:
{{ $image := .Resources.GetMatch .Params.featured_image }}
At this point, we have our image as a resource, but we haven’t created its display. To do so, we’ll go ahead and create an <img>
layout that calls in the resource.
First, we’ll open (and leave open) the image tag:
<img
Then, as with any image tag, we need to include the source for the file. This is extremely easy now that we’ve called the image as a resource and assigned it to a variable: we just need to add .RelPermalink
to get the URL.
The only slight twist is that we need to set the scope for the image’s source permalink so that we can also declare the alt text using another front matter parameter. So we’ll also include {{ with }} ... {{ end }}
around only the src
line:
<img
{{ with $image }}src="{{$image.RelPermalink }}"{{end}}
/>
Finally, we can also include alt text with our image using the featured_alt
parameter in the front matter:
alt="{{ .Params.featured_alt }}"
At the end, we just need to close the conditional statement we started at the beginning of the block with {{ end }}
. Here’s the whole block again:
{{ if isset .Params "featured_image" }}
{{ $image := .Resources.GetMatch .Params.featured_image }}
<img
{{ with $image }}src="{{$image.RelPermalink }}"{{end}}
alt="{{ .Params.featured_alt }}"
/>
{{ end }}
Once you’ve written this into your page layout file, all you’ll ever have to do is include the following two lines in your page front matter:
---
featured_image: image.jpg
featured_alt: "Your alt text."
---
It’s that easy!
But there’s more! We can expand our basic layout and front matter parameters to create an even more dynamic and intelligent image layout for a list page. This page will range over relevant content and return an optimized featured image. A good example of (actually the best example, since we’ll be recreating a small part of it) is the Posts page for this website.
First, let’s look at the markup / code for a full grid layout of blog posts. Ignore Tailwind CSS classes, which are irrelevant.
{{ range (.Paginate ( .Pages.ByDate.Reverse )).Pages }}
<article class="p-6 shadow-lg flex flex-col">
{{ if isset .Params "featured_image" }}
{{ $image := .Resources.GetMatch .Params.featured_image }}
<a href="{{ .RelPermalink }}">
<img
{{ with $image }}src="{{ (($image.Crop "1200x800").Resize "600x").RelPermalink }}"{{ end }}
alt="{{ .Params.featured_alt }}"/>
</a>
{{ else }}
{{ $image := resources.Get .Site.Params.post_image }}
<a href="{{ .RelPermalink }}">
<img
{{ with $image }}src="{{ (($image.Crop "1200x800").Resize "60x").RelPermalink }}" {{ end }}
alt="{{ .Site.Params.post_image_alt }}"
/>
</a>
{{ end }}
<div class="grow">
<h3 class="font-bold text-xl mt-2"><a href="{{ .RelPermalink }}">{{ .Title }}</a></h3>
<p class="mb-4">{{ .Params.summary }}</p>
</div>
<div>
<a href="{{ .RelPermalink }}" class="p-2 rounded-lg bg-indigo-600 text-white">Read More</a>
</div>
</article>
{{ end }}
To summarize what’s going on here: we’re creating a layout where Hugo will range over every relevant page and, for each one, produce a grid item containing:
The main thing you need to understand is that all of this is surrounded by:
{{ range (.Paginate ( .Pages.ByDate.Reverse )).Pages }}
...
{{{ end }}}
What this is stating (beyond some pagination information) is that Hugo is going to range over every relevant page and create whatever is included between these tags.
So, back to our focus: the featured image. We could simply copy the image layout we created above. But I want something beyond the basics. Specifically:
So how do we do all this? Let’s take a closer look at the image-related part of the markup / code above:
{{ if isset .Params "featured_image" }}
{{ $image := .Resources.GetMatch .Params.featured_image }}
<a href="{{ .RelPermalink }}">
<img
{{ with $image }}src="{{ (($image.Crop "1200x800").Resize "600x").RelPermalink }}"{{ end }}
alt="{{ .Params.featured_alt }}"
/>
</a>
{{ else }}
{{ $image := resources.Get .Site.Params.post_image }}
<a href="{{ .RelPermalink }}">
<img
{{ with $image }}src="{{ (($image.Crop "1200x800").Resize "600x").RelPermalink }}" {{ end }}
alt="{{ .Site.Params.post_image_alt }}"
/>
</a>
{{ end }}
Much of this should hopefully look familiar to you now. To summarize the first half of the block:
<a href="{{ .RelPermalink }}"></a>
tag so that clicking the link takes us to the page itself. Note that we’re still in the page scope, so .RelPermalink
is returning the link to the page, not the image.{{ with $image }}{{ end }}
.Now let’s look at the second half, which starts at the {{ else }}
line:
{{ else }}
{{ $image := resources.Get .Site.Params.post_image }}
<a href="{{ .RelPermalink }}">
<img
{{ with $image }}src="{{ (($image.Crop "1200x800").Resize "60x").RelPermalink }}" {{ end }}
alt="{{ .Site.Params.post_image_alt }}"
/>
</a>
{{ end }}
Note that we’re getting something different in our variable here: .Site.Params.post_image
. What this block is saying is that if a featured_image hasn’t been called on a page, Hugo should go get a default image declared in your configuration file title post_image
. Again, you can include also define the alt text there as post_image_alt
. In your config file, it would look like this:
post_image = 'images/image.png'
post_image_alt = 'Alt text.'
Note that I’m using a config.toml file, so the specific syntax might vary since Hugo allows for YAML, TOML, or JSON. This won’t have any effect on the layouts we’ve created.
We’re in the home stretch now. There’s one small piece of our markup / code that we haven’t talked about, but it’s doing some essential work. Note that we’ve added something to how we’ve called our image source:
{{ (($image.Crop "1200x800").Resize "600x").RelPermalink }}
We’re using some of Hugo’s image-processing methods here. Because we wanted equally sized images, we employ the .Crop
method to first take our image and crop it to 1200x800. Because the image will be used in a grid layout where it will be smaller, we then resize our cropped image to half size using the Resize
method. This will help with page load time. Finally, we’re returning the permalink for the now-processed image. You can and should tweak the crop size and the resize to your liking.
Once you’ve set up these layouts, the only thing you will ever have to write is:
featured_image
and featured_alt
parameters in your page front matter.post_image
and post_image_alt
in your config file as a fallback. Hopefully, this helped you learn something about using creating image layouts in Hugo.You can focus entirely on your content and let Hugo and your layouts do the work for you.
If you’ve got this down and want to do more with what you’ve learned, here are two small project ideas:
<img></img>
layouts using <figure></figure>
tags. See if you can add an optional parameter for figcaptions
.Finally, if you just want a simple and flexible Hugo theme that does all of this for you, check out Little Ghost.