Markdown Render Hooks for Link and Image in Hugo

Updated: , Published:

Problem statement

When you write link or image in markdown then it renders to fixed html format. And there is no way of customizing it without custom shortcodes in hugo.

For example the following markdown code

1
2
3
4
5
// Link
[Link text](https://apoorv.blog "Link title")

// Image
![Alt text](/image.jpg "Image title")

will generate the following html code

1
2
3
4
// Link
<a href="https://apoorv.blog" title="Link title">Link text</a>
// Image
<img src="https://apoorv.blog/image.jpg" alt="Alt text" title="Image title">

Sometimes you want default behavior for all links e.g. All links must open in separate browser tab. Similarly you may want default behavior for every image e.g. Every image is a link pointing to the actual image file. If some part of image is not clear then you can open full size image and zoom into it.

Now you can do the same with Markdown Render Hooks. And the cherry on top is that it renders 15% faster and requires less memory.

Prerequisite

  • You need min Hugo 0.62 or above installed on your computer.
  • You need to use Goldmark as your markdown renderer.

Usage

In very short you are going to define how all markdown links and images should be rendered to html using single template file for each. So you have direct control over how markdown is rendered. Create two new files as following.

1
2
3
4
5
layouts
└── _default
    └── _markup
        └── render-link.html
        └── render-image.html

Then inside the render-link.html write following code. One thing I personally like to use it for is to open non https links in new tab.

1
2
3
4
5
{{ $isInsecure := true }}
{{ if strings.HasPrefix .Destination "https" }}
    {{ $isInsecure = false }}
{{ end }}
<a href="{{ .Destination }}"{{ with .Title}} title="{{ . }}"{{ end }}{{ if $isInsecure }} target="_blank" rel="noopener"{{ end }}>{{ .PlainText }}</a>

So let’s talk about properties available in the template

  • .Destination is the link url and required
  • .Title is the optional title field.
  • .PlainText is the link text and required.

If you want to open link in new tab then you need to set target="_blank". And rel="noopener" is for your security. So the link website cannot access referring website (your website) through browser. This is commonly done when you don’t trust the link website or if you are opening link in different browser tab.

In case of images personally I don’t keep images in static folder. I make use of Page Bundles to store all page specific images. Link and image template both have access to .Page variable. Write the following code inside render-image.html.

1
2
3
4
5
6
7
8
{{ $myImg := .Page.Resources.GetMatch .Destination }}
{{ $altText := .PlainText }}
{{ $imgTitle := .Title }}
{{ with $myImg }}
<img src="{{ .Permalink }}" alt="{{ $altText }}" {{ with $imgTitle }} title="{{ . }}" {{ end }} width="{{ .Width }}" height="{{ .Height }}">
{{ else }}
    <p>Image not found</p>
{{ end }}

So just based on image name you can find image from all page images. And with that image you can get width and height for each image.

Accessability considerations

There are 2 considerations required for link accessability

  1. Link text should stand out from regular text
  2. Show icon if you are opening link on new browser tab

After many year’s of internet, people have come to terms that any text that has underline is a link. So please don’t remove default link underline with CSS. If you really have to remove underline then make sure underline appears on :hover and :focus.

Now just having underline is not enough. You need 3:1 contrast from link text color and surrounding non-link text color. Now you maybe inclined to use opposite color for link to make link stand out. So you are solely relying on color to convey meaning. But this will not work for color blind people. So changing color is good but please don’t rely on color to convey meaning.

Instead you can make text bold or italics in markdown itself. But .PlainText would remove all the formatting. And if you want markdown to be rendered to html before adding to template then make following changes in render-link.html.

1
2
3
4
5
{{ $isInsecure := true }}
{{ if strings.HasPrefix .Destination "https" }}
    {{ $isInsecure = false }}
{{ end }}
<a href="{{ .Destination }}"{{ with .Title}} title="{{ . }}"{{ end }}{{ if $isInsecure }} target="_blank" rel="noopener"{{ end }}>{{ .Text | safeHTML }}</a>

With .Text the markdown written inside link text is rendered before adding to the link template. So the following markdown

1
[**Link text**](https://apoorv.blog "Link title")

will generate following link

1
<a href="https://apoorv.blog" title="Link title"><strong>Link text</strong></a>

Don’t forget to convert to safeHTML otherwise it will show <strong> tag in final text.

Also for accessibility its important that you open link in the same browser tab. It’s not recommended to open link in separate tab. But if you really have to open link in different browser tab then you can show an icon image with alt text for screen readers like following example.

1
2
3
4
5
{{ $isInsecure := true }}
{{ if strings.HasPrefix .Destination "https" }}
    {{ $isInsecure = false }}
{{ end }}
<a href="{{ .Destination }}"{{ with .Title}} title="{{ . }}"{{ end }}{{ if $isInsecure }} target="_blank" rel="noopener"{{ end }}>{{ .Text | safeHTML }}{{ if $isInsecure }}<img alt="Open in new tab" src="icon.png" width="10" height="10" />{{ end }}</a>

I am setting fixed width and height because I don’t want to change icon size on any screen size.

Have you found any mistake or improvement for this post then let me know in the comments below.

Load more