Sam Hooke

Automatic image thumbnails in Hugo from static directory

Hugo v0.32 introduced image processing, which can be used to resize, fit or fill images. This can be useful to, for example, automatically create a low resolution thumbnail from a high resolution image. Hugo v0.34 (latest at time of writing) modified the API for image processing.

Using the image processing in Hugo v0.34, my goal is this:

  1. Store my full resolution “original” images in the static directory (/static/img/)
  2. Use Hugo to automatically generate low resolution “thumbnail” images
  3. Use a shortcode to include the thumbnail image in markdown as a link to the original image

Shortcodes §

Hugo image processing can be used directly in Hugo templates, but there is no syntax for using it within markdown. The solution is to use Hugo shortcodes.

Shortcodes let us include a Hugo template with a markdown file. For example, using the following post and shortcode…

/content/post/my-post.md§
{{< my-shortcode "hello, world!" 3 >}}
/layouts/shortcodes/my-shortcode.html§
{{ $text := .Get 0 }}
{{ $num := .Get 1 }}
<ul>
{{ range (seq $num) }}
  <li>{{ $text }}</li>
{{ end }}
</ul>

…will cause Hugo to render a web page as follows:

Rendered page: §
<ul>
  <li>hello, world!</li>
  <li>hello, world!</li>
  <li>hello, world!</li>
</ul>

Page Resources §

The Hugo v0.32 release notes include an example shortcode which addresses goals 2 and 3 I outlined above, but does not address goal 1. Further, I wanted to write the shortcode myself to help me better understand how everything fits together.

Goal 1 is not addressed by the aforementioned example because it relies upon including your images within your /content/ directory rather than within your /static/ directory. Using the static directory is desirable for me because I want the option to be able to reference the same image from different posts, rather than assuming a one-to-one relationship between each image and a post.

We can use .Page.Resources within a template to find resources (e.g. images) within that post. For example, given the following directory structure:

my-hugo-site/
├── config.toml
├── content/
│   └── post/
│      └── example-post/
│         ├── index.md
│         └── images/
│             ├── image_1.jpg
│             └── image_2.jpg
├── layouts/
│  └── shortcodes/
│      └── list-images.html
└ static/
   └── img/
       ├── image_3.jpg
       └── image_4.jpg

If we create a shortcode to list all image resources:

/layouts/shortcodes/list-images.html§
<ul>
{{ range .Page.Resources }}
  <li><a href="{{ .RelPermalink }}">{{ .ResourceType | title }}</a></li>
{{ end }}
</ul>

And then use this shortcode within our example post:

/post/example-post/index.md§
{{< list-images >}}

We get the following page rendered:

Rendered page: §
<ul>
  <li><a href="/post/example-post/images/image_1.jpg">Image</a></li>
  <li><a href="/post/example-post/images/image_2.jpg">Image</a></li>
</ul>

However it appears there is no way to get .Page.Resources to search in our /static/img/ directory. There is a proposal to add a .Site.Resources but as of Hugo v0.34 it is just not possible.

So there you have it. Not currently possible.

Or is it?

Hacky solution §

Alright…

Let’s alter our structure from earlier to create a new “fakestatic” section with a “fakepost” which contains all our static content:

my-hugo-site/
├── config.toml
├── content/
│   ├── post/
│   │  └── example-post/
│   │     └── index.md
│   └── fakestatic/
│      └── fakepost/
│         ├── index.md
│         └── images/
│             ├── image_1.jpg
│             ├── image_2.jpg
│             ├── image_3.jpg
│             └── image_4.jpg
└── layouts/
    └── shortcodes/
        └── list-fakestatic-images.html

If we take our example shortcode from earlier and take advantage of the GetPage function, we can list the images from “fakepost” in any other post.

/layouts/shortcodes/list-fakestatic-images.html§
{{ $fakestatic := .Site.GetPage "page" "fakestatic/fakepost/index.md" }}
<ul>
{{ range $fakestatic.Resources }}
  <li><a href="{{ .RelPermalink }}">{{ .ResourceType | title }}</a></li>
{{ end }}
</ul>

The above shows that we can access the resources of one post from another. Next, we create a shortcode that accepts the image name as an argument and displays the said image:

/layouts/shortcodes/img-fakestatic.html§
{{ $fakestatic := .Site.GetPage "page" "fakestatic/fakepost/index.md" }}
{{ $imagename := printf "images/%s*" (.Get 0) }}
{{ $image := $fakestatic.Resources.GetMatch $imagename }}
<img src="{{ $image.RelPermalink }}">

We can then use the above to include any arbitrary image from /content/fakestatic/fakepost/images/ in any markdown post:

{{< img-fakestatic "image_4.jpg" >}}

Adding the thumbnail §

/layouts/shortcodes/img-thumb-cap.html§
{{ $fakestatic := .Site.GetPage "page" "fakestatic/fakepost/index.md" }}
{{ $imagename := printf "images/%s*" (.Get 0) }}
{{ $size := .Get 1 }}
{{ $imageoriginal := $fakestatic.Resources.GetMatch $imagename }}
{{ $imagethumbnail := $imageoriginal.Resize $size }}
<a href="{{ $imageoriginal.RelPermalink}}">
<img src="{{ $imagethumbnail.RelPermalink }}">
</a>
/content/post/example-post/index.md§
{{< img-thumb-cap "image_4.jpg" "200x" >}}

Adding a caption and some style §

We can make things a bit more interesting by adding an optional caption and styling.

/layouts/shortcodes/img-thumb-cap.html§
{{ $fakestatic := .Site.GetPage "page" "fakestatic/fakepost/index.md" }}
{{ $imagename := printf "images/%s*" (.Get 0) }}
{{ $size := .Get 1 }}
{{ $imageoriginal := $fakestatic.Resources.GetMatch $imagename }}
{{ $imagethumbnail := $imageoriginal.Resize $size }}
<figure>
  <a href="{{ $imageoriginal.RelPermalink}}">
    <img src="{{ $imagethumbnail.RelPermalink }}">
  </a>
  <figcaption>
    {{ with .Inner }}
    {{ . }}
    {{ end }}
  </figcaption>
</figure>
/content/post/example-post/index/md §
{{< img-thumb-cap "egg_04.jpg" "200x" >}}A bowl of molten chocolate{{< /img-thumb-cap >}}
/static/css/style.css§

To show the above in action:

alt-text
A bowl of molten chocolate

See all notes.

← Previous Note: Captured `SystemExit` with pytest and tox
Next Note: Escaping Hugo shortcodes within Hugo markdown →