Hugo Responsive Images With Markdown Render Hook



I recently learned about the srcset attribute on the img tag. Essentially, if you want to have responsive images then it’s better to serve multiple resolutions of the image and let the browser decide on which is the best (rather than relying on CSS to downscale) It can help the loading times of a webpage too. For mobile viewers, I can serve a 20KB image that’s 800x400 pixels. Then for desktop viewers, I can serve a 1.5MB image that’s 1920x1080.

Responsive Images in HTML Overview

This can be accomplished in one of two ways. The first way is just using the image tag with srcset and sizes attribute. Here’s an example from MDN:

<img srcset="elva-fairy-480w.jpg 480w,
             elva-fairy-800w.jpg 800w"
     sizes="(max-width: 600px) 480px,
            800px"
     src="elva-fairy-800w.jpg"
     alt="Elva dressed as a fairy">

Up until 600px screen width (CSS media query), there will be 480px width slot for the image to fit. Therefore, the browser will choose the 480w image. Anything beyond that and thr browser will choose the 800w image.

Another way of doing this is using the picture element . In this case, you define an outer picture container with multiple sources. Another example from MDN:

<picture>
  <source srcset="logo-768.png 768w, logo-768-1.5x.png 1.5x">
  <source srcset="logo-480.png, logo-480-2x.png 2x">
  <img src="logo-320.png" alt="logo">
</picture>

I personally like the second way. It seems a bit cleaner and easier to follow.

Hugo Responsive Images

To implement this in hugo, you can either make it a shortcode that you embed in your post:

{< img src="evo-4g.jpg" title="Holding my first Android Smartphone: The HTC EVO 4G" >}

Or simply attach to the new goldmark render hooks so that you can keep using the same markdown image tag:

![Some Image]("some_image.jpg")

Let’s attach it to the render hooks so that we can keep using the same markdown style. To do this, you will need to make sure that you directory structure is as follows:

content/
├── about
│   ├── index.md
├── posts
│   ├── my-post
│   │   ├── content1.md
│   │   ├── content2.md
│   │   ├── image1.jpg
│   │   ├── image2.png
│   │   └── index.md
│   └── my-other-post
│       └── index.md

Now create the following directory path: layouts/_default/_markup/render-image.html. In that render-image.html add the following code:


{{/* Original code from: https://laurakalbag.com/processing-responsive-images-with-hugo/   */}}
{{/* Just modified a bit to work with render_image hook and output webp images   */}}
{{/* get file that matches the filename as specified as src=""  */}}
{{ $src := .Page.Resources.GetMatch (printf "%s" (.Destination | safeURL))  }}
{{ $alt := .PlainText | safeHTML }}

{{/* So for posts that aren't setup in the page bundles, it doesn't fail  */}}
{{ if $src }}


{{ $tinyw := default "500x webp" }}
{{ $smallw := default "800x webp" }}
{{ $mediumw := default "1200x webp" }}
{{ $largew := default "1500x webp" }}

{{/* resize the src image to the given sizes */}}
{{/* We create a a temp scratch because it's not available in this context */}}
{{ $data := newScratch }}
{{ $data.Set "tiny" ($src.Resize $tinyw) }}
{{ $data.Set "small" ($src.Resize $smallw) }}
{{ $data.Set "medium" ($src.Resize $mediumw) }}
{{ $data.Set "large" ($src.Resize $largew) }}

{{/* add the processed images to the scratch */}}

{{ $tiny := $data.Get "tiny" }}
{{ $small := $data.Get "small" }}
{{ $medium := $data.Get "medium" }}
{{ $large := $data.Get "large" }}

{{/* only use images smaller than or equal to the src (original) 
image size, as Hugo will upscale small images */}}


<a href="{{ $src.RelPermalink }}">
    <picture>
    
    <source media="(max-width: 376px)" 
        srcset="{{with $tiny.RelPermalink }}{{.}}{{ end }}">

    <source media="(max-width: 992px)" 
        srcset="{{with $small.RelPermalink }}{{.}}{{ end }}">

    <source media="(max-width: 1400px)" 
        srcset="{{with $medium.RelPermalink }}{{.}}{{ end }}">

    <source media="(min-width: 1600px)" 
        srcset="{{with $large.RelPermalink }}{{.}}{{ end }}">
    
    <img 
 
   alt="{{ $alt }}" title="{{ $alt }}" src="{{ $src }}" 
   height="{{ $src.Height}}" width="{{ $src.Width }}" class="img-fluid">

</picture>
</a>
  
  {{/* Since I do image-response class, the only thing that really 
  matters is the height and width matches the image aspect ratio */}}

  {{ end }}

A couple of points. First, I also convert all the images to webp format (you need Hugo extended installed for that). Secondly, I also set the explicit height and width of the image tag to the source. Lighthouse recommends that img elements have a height and width set so the browser can keep that gap open and not wait for CSS to figure out that size. What really matter is that the aspect ratio is the same.

So now when you check your html source code, you should see the following:

<a href="https://bennettnotes.com/post/obsessed-with-success-and-prestige/cscareerquestions.png" class="active">
    <picture>
            <source media="(max-width: 376px)" srcset="https://bennettnotes.com/post/obsessed-with-success-and-prestige/cscareerquestions_hu1e8f3c21c9fe58606fcf5407036ba6cd_34339_500x0_resize_q75_h2_box_3.webp">
            <source media="(max-width: 992px)" srcset="https://bennettnotes.com/post/obsessed-with-success-and-prestige/cscareerquestions_hu1e8f3c21c9fe58606fcf5407036ba6cd_34339_800x0_resize_q75_h2_box_3.webp">
            <source media="(max-width: 1400px)" srcset="https://bennettnotes.com/post/obsessed-with-success-and-prestige/cscareerquestions_hu1e8f3c21c9fe58606fcf5407036ba6cd_34339_1200x0_resize_q75_h2_box_3.webp">
            <source media="(min-width: 1600px)" srcset="https://bennettnotes.com/post/obsessed-with-success-and-prestige/cscareerquestions_hu1e8f3c21c9fe58606fcf5407036ba6cd_34339_1500x0_resize_q75_h2_box_3.webp">
        <img alt="" src="cscareerquestions.png" height="391" width="1321" class="img-fluid">
    </picture>
</a>