Hugo Image Shortcode Post Processing

August 8, 2023
Hugo webp lifehacks

The Problem

I generated this website using a static website generator called Hugo. Long live statically generated websites. So long in fact we need to update some things and get with the times. Image embeds have always been a tricky think in web experiences, we want quality, both in the image and of the experience using the site. I really like the longevity and packaging of a single monolithic repo with all my source content at the best quality available. I also like the idea of the static generator taking this source material and preparing it for consumption on the internet.

massive megalodon shark with laser guns jumping out of the ocean attacking a diver along the seaside of a modern tech city

If we throw a mega-megalodon sized image into the repo, It’s not reasonable to expect all internet users to want to grab a single 4mb+ asset. So let’s do something about it!

Automating an Image Pipeline

I had been using this shortcode approach in all my markdown( the {{foo}} brackets are removed so it doesn’t render;

< bundle-image name="image.jpeg" alt="Describe the Image" >

This then allows me to place content like so;

repo
   |
   \content
          |
          \logs # category for all my blog style log posts
              |
              \hugo-image-shortcode-post-processing # one folder per page
                                                   |
                                                   \index.md # markdown page content
                                                   \image.jpg # an image

Great, so for a while I just served up my source images to any victim that came by this fine establishment. I then some years ago moved to adopt the srcset html approach which allows me to serve up multiple versions of an image and let browsers decide what poison they prefer.

Initially, I followed this set of instructions from Nils;

<!-- repo/themes/footheme/layouts/shortcodes/bundle-image.html -->
{{ $altText := .Get "alt"}}
{{ $caption := .Get "caption"}}
{{ with $.Page.Resources.GetMatch (.Get "name") }}
  <figure>
    <a href="{{.RelPermalink}}">
      <img
        srcset="
          {{ (.Resize "320x").RelPermalink }} 320w,
          {{ (.Resize "600x").RelPermalink }} 600w,
          {{ (.Resize "1200x").RelPermalink }} 2x"
        src="{{ (.Resize "600x").RelPermalink }}" alt="{{$altText}}"/>
    </a>
    <figcaption><p>{{ $caption }}</p></figcaption>
  </figure>
{{ else }}
  could not find image
{{ end }}

Adding Modern Shiny Rock Compression Formats

a large solid shiny rock in the middle of the grass field lit with sun rays from a gap in the clouds, people worshiping around the rock in amazement

This worked great, but as times had changed, it became more useful to just serve up a few basic formats rather than a bunch of different sizes. So I now use this;

<!-- repo/themes/footheme/layouts/shortcodes/bundle-image.html -->
{{ $altText := .Get "alt"}}
{{ $caption := .Get "caption"}}
{{ with $.Page.Resources.GetMatch (.Get "name") }}
  <figure>
    <a href="{{.RelPermalink}}">
      <img
        srcset="
          {{ (.Resize "2000x webp q90").RelPermalink }} 2x,
          {{ (.Resize "1000x webp q90").RelPermalink }} 1x,
          {{ (.Resize "2000x png").RelPermalink }} 2x"
        src="{{ (.Resize "400x jpg q60").RelPermalink }}" alt="{{$altText}}" defer/>
    </a>
    <figcaption><p>{{ $caption }}</p></figcaption>
  </figure>
{{ else }}
  could not find image
{{ end }}

It gives me a modern webp medium and large option, medium png option for high-quality compatibility, and a fallback on small jpg which should work on anything. I also added in defer to help with lighthouse.

The png file is massive, webp small, and jpg is just a rounding error. Users can have a consistent experience visiting the site without compatibility issues, or having to compromise for potato images.

The documentation from Hugo made it really clear how to do this with a single asset/image, but less clear how to do it for all images across an entire site. Hopefully this will be useful to someone trying to do the same or similar.

This all results in html generated for the browser that looks like the following;

<figure>
  <a href=/logs/hugo-image-shortcode-post-processing/megalodan-image.png>
    <img srcset="/logs/hugo-image-shortcode-post-processing/megalodan-image_2000x0_resize_q90_h2_linear_3.webp 2x,
/logs/hugo-image-shortcode-post-processing/megalodan-image_1000x0_resize_q90_h2_linear_3.webp 1x,
/logs/hugo-image-shortcode-post-processing/megalodan-image_1000x0_resize_linear_3.png 2x" src=/logs/hugo-image-shortcode-post-processing/megalodan-image_400x0_resize_q60_bgffffff_linear_3.jpg alt="massive megalodon shark with laser guns jumping out of the ocean attacking a diver along the seaside of a modern tech city" defer>
  </a>
  <figcaption>
    <p></p>
  </figcaption>
</figure>

Put another way, we’ve got one source image auto-converted into 4 display image formats. Using the above megalodon source material;

Source megalodan-image.png: 3.1 MB
2000 pixel max width quality 90% webp: 651 KB
1000 pixel max width quality 90% webp: 215 KB
1000 pixel max width png: 1.5 MB
400 pixel max width quality 60% jpg: 27 KB

I’d call that a win. See you all in 5 years when we’ve invented a new compression algorithm inspired by cellular peanut casing rigidity structures biomimicry.