Enhanced Image Rendering in Hugo

Alt One of the things I love about Hugo is its flexibility when it comes to Markdown rendering. While the default behavior for images is usually enough, I found myself wanting more control. Specifically the ability to add CSS classes or wrap images in links without resorting to raw HTML in my Markdown files (as hugo raises warnings about this).

To solve this, I started using a custom render-image.html template. It allows me to stay in “Markdown mode” while still getting the benefits of optimized, flexible image output.

The Problem

Standard Markdown image syntax is simple: ![Alt text](image.jpg "Title text"). However, it doesn’t provide a native way to:

  1. Add a CSS class for styling (e.g., centering an image or adding a border).
  2. Link the image to another page or a larger version of itself.
  3. Automatically handle Hugo’s Image Processing for local resources.

The Solution: A Custom Render Hook

Hugo allows you to override how Markdown elements are rendered by creating a “render hook”. By placing a file at layouts/_default/_markup/render-image.html, we can intercept every image in our Markdown and process it through our own logic.

Advanced Syntax

My template extends the “Title” part of the image syntax to support extra attributes. I use a # separator to define classes and a | to define a link.

Basic Image: ![Alt](image.jpg "My Title")

Image with a CSS Class: ![Alt](image.jpg "My Title#featured-image")

Image with a Class and a Link: ![Alt](image.jpg "My Title#center-img|https://example.com")

How It Works

The template does a few smart things:

  • Resource Resolution: It first checks if the image is a Page Resource. If not, it looks in the global assets via resources.Get. If it’s still not found (like an external URL), it falls back to the provided destination.
  • Title Parsing: it splits the .Title string by # and | to extract the actual title, the CSS class, and the link URL.
  • Performance: It automatically adds loading="lazy" and decoding="async" to every image, which is great for Core Web Vitals.
  • Dimensions: If the image is found as a Hugo resource, it automatically populates the width and height attributes to prevent layout shifts.

The Template

Here is the full render-image.html template. You can drop this into your Hugo project at layouts/_default/_markup/render-image.html.

 1{{- $dest := .Destination -}}
 2{{- $class := .Attributes.class -}}
 3{{- $link := .Attributes.link -}}
 4
 5{{- if (strings.Contains .Title "#") -}}
 6{{- $parts := split .Title "#" -}}
 7{{- .Page.Scratch.Set "title" (index $parts 0) -}}
 8{{- $fragment := (index $parts 1) -}}
 9{{- if (strings.Contains $fragment "|") -}}
10{{- $fragParts := split $fragment "|" -}}
11{{- $class = (index $fragParts 0) -}}
12{{- $link = (index $fragParts 1) -}}
13{{- else -}}
14{{- $class = $fragment -}}
15{{- end -}}
16{{- else -}}
17{{- .Page.Scratch.Set "title" .Title -}}
18{{- end -}}
19{{- $title := .Page.Scratch.Get "title" -}}
20
21{{- $img := .Page.Resources.GetMatch $dest -}}
22{{- if not $img -}}
23{{- $img = resources.Get $dest -}}
24{{- end -}}
25
26{{- if $img -}}
27{{- $processed := $img -}}
28{{- if $link -}}
29<a href="{{ $link | safeURL }}">
30    {{- end -}}
31    <img src="{{ $processed.RelPermalink }}"
32         width="{{ $processed.Width }}"
33         height="{{ $processed.Height }}"
34         {{ with .Text }}alt="{{ . }}" {{ else }}alt="" {{ end }}
35         {{ with $title }}title="{{ . }}" {{ end }}
36         {{ with $class }}class="{{ . }}" {{ end }}
37         loading="lazy"
38         decoding="async"
39    >
40    {{- if $link -}}
41</a>
42{{- end -}}
43{{- else -}}
44{{- if $link -}}
45<a href="{{ $link | safeURL }}">
46    {{- end -}}
47    <img src="{{ $dest | safeURL }}"
48         {{ with .Text }}alt="{{ . }}" {{ else }}alt="" {{ end }}
49         {{ with $title }}title="{{ . }}" {{ end }}
50         {{ with $class }}class="{{ . }}" {{ end }}
51         loading="lazy"
52         decoding="async"
53    >
54    {{- if $link -}}
55</a>
56{{- end -}}
57{{- end -}}

Hope you find it useful!