Hugo Render Hooks Explained

Markdown is intentionally simple.

You write:

[My website](https://example.com/)

and Hugo turns it into HTML.

Usually that is exactly what you want.

But sometimes you want more control. Maybe external links need a class. Maybe Markdown images should become responsive images. Maybe headings need custom anchor links.

Hugo render hooks let you change how Markdown elements are rendered without changing the Markdown itself.

Hugo documents the feature under Markdown render hooks.

Create:

layouts/
└── _default/
    └── _markup/
        └── render-link.html

A simple version:

<a href="{{ .Destination | safeURL }}">
  {{ .Text | safeHTML }}
</a>

Now Hugo uses this template when rendering Markdown links.

{{ $external := strings.HasPrefix .Destination "http" }}

<a
  href="{{ .Destination | safeURL }}"
  {{ if $external }}
    rel="noopener noreferrer"
  {{ end }}
>
  {{ .Text | safeHTML }}
</a>

You may also add a class:

class="{{ if $external }}external-link{{ end }}"

Then:

.external-link::after {
  content: " ↗";
}

Be careful with new tabs

Some sites automatically add:

target="_blank"

to every external link.

I generally do not think that should be the default.

If you do use it, add:

rel="noopener noreferrer"

and think about whether users actually benefit from the forced new tab.

Image render hooks

Markdown:

![Flamingo at Riga Zoo](flamingo.jpg)

normally becomes a basic image.

A render hook can add responsive images, lazy loading, dimensions, figure markup, or captions.

<figure>
  <img
    src="{{ .Destination | safeURL }}"
    alt="{{ .Text }}"
    loading="lazy"
  >

  {{ with .Title }}
    <figcaption>{{ . }}</figcaption>
  {{ end }}
</figure>

Now:

![Flamingo](flamingo.jpg "Riga Zoo")

can produce a figure with a caption.

Process page resources

If the image lives in a page bundle:

{{ $image := .Page.Resources.GetMatch .Destination }}

Then:

{{ $small := $image.Resize "400x webp" }}
{{ $medium := $image.Resize "800x webp" }}

This is a very useful way to make Markdown images responsive automatically.

Heading render hooks

You can also control headings.

Create:

render-heading.html

Then:

<h{{ .Level }} id="{{ .Anchor }}">
  {{ .Text | safeHTML }}
</h{{ .Level }}>

Add a direct link:

<h{{ .Level }} id="{{ .Anchor }}">
  {{ .Text | safeHTML }}

  <a
    class="heading-anchor"
    href="#{{ .Anchor }}"
    aria-label="Link to this section">
    #
  </a>
</h{{ .Level }}>

This is useful for long guides and documentation.

Keep the Markdown clean

The content can remain simple:

## My heading

Read [the documentation](https://example.com/).

![Example image](example.jpg)

The presentation logic stays in Hugo templates.

That makes the content easier to move later.

Render hooks are site-wide behaviour

A render hook can affect every matching Markdown element.

That is powerful, but you should test changes carefully.

For links, check:

For images, check:

Add fallbacks

For image processing, the resource may not exist.

{{ $image := .Page.Resources.GetMatch .Destination }}

{{ if $image }}
  <!-- Process image -->
{{ else }}
  <img
    src="{{ .Destination | safeURL }}"
    alt="{{ .Text }}"
  >
{{ end }}

That keeps external or unusual images working.

Where render hooks are most useful

Links    → external-link handling
Images   → srcset, dimensions, lazy loading
Headings → anchor links
Code     → wrappers or labels

You can do much more, but these cover a lot of real sites.

Render hooks are easy to miss because Markdown already works without them. But once a Hugo site grows, they can remove a lot of repeated work.

Instead of teaching every article how an image or link should be rendered, you teach Hugo once.