Hugo figure shortcode: image renders, anchor link doesn't work

Hello, I’m using the leaf bundle to organize my blog posts in Hugo, and my directory structure is set up as follows:

+ content
   + posts
      + 2024
          + 2024-08-10-first-post
                - orchid.jpg
                - index.md
...
front matter
...

{{< figure loading="lazy" src="orchid.jpg" link="orchid.jpg" alt="orchid" >}}

Summary

<!--more-->

Content

As you can see above, I have an image inserted using Hugo’s built-in figure shortcode. The src attribute is correctly processed, and its value is always properly assigned. In the browser’s web inspector its value shows as: src="/posts/2024/08/first-post/orchid.jpg"

However, when viewing the blog posts listing (where the site’s URL is /posts/), the image link (I mean the value of the link="" attribute) is generated as /posts/orchid.jpg, which results in a “not found” error when clicking on the image because this path is incorrect.

On the other hand, when I click on the blog post itself to view it in detail (rendered by the single.html template), the link is correctly resolved to /posts/2024/08/first-post/orchid.jpg.

I’m wondering if it’s possible to make the link URL behave consistently like the src attribute in Hugo’s figure shortcode. Specifically, I want the image link to always point to the correct path within the page bundle, such as /posts/2024/08/first-post/orchid.jpg, regardless of whether I’m viewing the /posts/ page or the /posts/2024/08/first-post/ page.

Thank you.

When you use the embedded figure shortcode on your site, do you always want to link the image file? Either way we’ll need to override the embedded template, but if you always want to link to the image we can simplify both the template and your markdown.

Yes, I always want to link to the image file.

First, override the embedded template with your own:
https://gohugo.io/content-management/shortcodes/#figure

To override Hugo’s embedded figure shortcode, copy the source code to a file with the same name in the layouts/shortcodes directory.

mkdir layouts/shortcodes
touch layouts/shortcodes/figure.html

Then copy the source code referenced above into your new file.

Then modify the code to do what you want.

layouts/shortcodes/figure.html
<figure{{ with .Get "class" }} class="{{ . }}"{{ end }}>
  {{- $u := urls.Parse (.Get "src") -}}
  {{- $src := $u.String -}}
  {{- if not $u.IsAbs -}}
    {{- with or (.Page.Resources.Get $u.Path) (resources.Get $u.Path) -}}
      {{- $src = .RelPermalink -}}
    {{- end -}}
  {{- end -}}
  <a href="{{ $src }}"{{ with .Get "target" }} target="{{ . }}"{{ end }}{{ with .Get "rel" }} rel="{{ . }}"{{ end }}>
  <img src="{{ $src }}"
    {{- if or (.Get "alt") (.Get "caption") }}
    alt="{{ with .Get "alt" }}{{ . }}{{ else }}{{ .Get "caption" | markdownify| plainify }}{{ end }}"
    {{- end -}}
    {{- with .Get "width" }} width="{{ . }}"{{ end -}}
    {{- with .Get "height" }} height="{{ . }}"{{ end -}}
    {{- with .Get "loading" }} loading="{{ . }}"{{ end -}}
  ><!-- Closing img tag -->
  </a>
  {{- if or (or (.Get "title") (.Get "caption")) (.Get "attr") -}}
    <figcaption>
      {{ with (.Get "title") -}}
        <h4>{{ . }}</h4>
      {{- end -}}
      {{- if or (.Get "caption") (.Get "attr") -}}<p>
        {{- .Get "caption" | markdownify -}}
        {{- with .Get "attrlink" }}
          <a href="{{ . }}">
        {{- end -}}
        {{- .Get "attr" | markdownify -}}
        {{- if .Get "attrlink" }}</a>{{ end }}</p>
      {{- end }}
    </figcaption>
  {{- end }}
</figure>

Your markdown should look like this:

{{< figure loading="lazy" src="orchid.jpg" alt="orchid" >}}

I do not like the way the embedded template is structured/formatted, but that’s an issue for another day.

1 Like

Thank you. It works. I don’t want to be rude, you already helped me a lot, but I want to ask if, in the case where I want the image on the listing page layout/posts/list.html to open the post (link to the post), just like the post title does, and after opening the post layout/posts/list.html then the image to link to the image. Could this be implemented by just modifying the figure.html template as you mentioned before, or would it involve more files to be edited?

Yes, you can handle this in the figure shortcode by referencing the Page object.

See https://gohugo.io/methods/shortcode/page/.

See if you can get it working yourself. I’m out for a few hours but will revisit this (if you have any problems) when I return.

1 Like

Earlier, when you asked if I wanted the image to always link to itself, I was wrong. Sorry. As I mentioned before, I would like the image to open the post when it appears on a listing page, and to open the image itself when it’s on a regular post page.

It seems to me that {{ .Page.RelPermalink }} provides the link to the post, but I’m not sure how to use a conditional statement to apply {{ .Page.RelPermalink }} only on a listing page, and {{ $src }} otherwise, as you did before. If that’s the right way of doing it. I got stuck.

<figure{{ with .Get "class" }} class="{{ . }}"{{ end }}>

  {{- $u := urls.Parse (.Get "src") -}}
  {{- $src := $u.String -}}
  {{- if not $u.IsAbs -}}
    {{- with or (.Page.Resources.Get $u.Path) (resources.Get $u.Path) -}}
      {{- $src = .RelPermalink -}}
    {{- end -}}
  {{- end -}}

  <a href="{{ conditional here }}{{ .Page.RelPermalink }}{{ else }}{{ $src }}{{ end }}"{{ with .Get "target" }} target="{{ . }}"{{ end }}{{ with .Get "rel" }} rel="{{ . }}"{{ end }}>
  <img src="{{ $src }}"
    {{- if or (.Get "alt") (.Get "caption") }}
    alt="{{ with .Get "alt" }}{{ . }}{{ else }}{{ .Get "caption" | markdownify| plainify }}{{ end }}"
    {{- end -}}
    {{- with .Get "width" }} width="{{ . }}"{{ end -}}
    {{- with .Get "height" }} height="{{ . }}"{{ end -}}
    {{- with .Get "loading" }} loading="{{ . }}"{{ end -}}
  ><!-- Closing img tag -->
  </a>

Using a shortcode, it is not possible[1] to do what you want. A shortcode is rendered once, then cached. The result of any given short code call cannot vary by context; it’s fixed.

To include an image with items on a list page, you would typically grab the image resource and perform the image processing within the list page template.


  1. It may be possible in the future if https://github.com/gohugoio/hugo/pull/12759 is merged at some point. ↩︎

1 Like

I would like each post to include an image in its summary. Here is a sample website: https://zoltan.onrender.com On the front page, I prefer that clicking on this image either opens the post or opens nothing. However, once the post has been clicked and opened, I want the image to open itself when selected. From what I understand, this can’t be achieved with a shortcode, right?

That is correct.

I tried another alternative. I added a line in the front matter of each post:

postimage: animage.jpg

and then I added it in list.html

<article class="blog-post">
      <header>
        <h3>
          <a href="{{ .RelPermalink }}">{{ .Title }}</a>
        </h3>
        <p>
          {{ with .Params.tags }}
            {{ range . }}
            <a class="tag" href="/tags/{{ . | urlize }}">{{ . }}</a>
            {{ end }}
          {{ end }}
        </p>
      </header>

>      {{ if .Params.postimage }}
>       <a href="{{ .RelPermalink }}">
>         <img src="{{ .RelPermalink }}/{{ .Params.postimage }}">
>       </a>
>       {{ end }}
      
      <section class="post-summary">
        <p>{{ .Summary }}</p>
      </section>

And this code into single.htm

<section class="blog">
  <div class="blog-container">
    <article class="blog-post">
      <header>
        <h2>{{ .Title }}</h2>
        <time datetime="{{ .Date | dateFormat "2006-01-02" }}">{{ .Date | dateFormat "January 2, 2006" }}</time>
      </header>

>     {{ if .Params.postimage }}
>       <a href="{{ .Params.postimage }}">
>         <img src="{{ .Params.postimage }}">
>       </a>
>       {{ end }}

      <section class="post-content">
        {{ .Content }}
      </section>

I’m not sure if I did right, but it seems to work. The image file is located in the content folder in the leaf bundle together with the content.

I added this to list.html template, which is used to list blog posts:

First part:

{{ if .Resources.GetMatch "featured.*" }}
   <section class="featured-image">
     <a href="{{ .Permalink }}">
       <img src="{{ (.Resources.GetMatch "featured.*").RelPermalink }}">
     </a>
   </section>
   {{ else if (isset .Params "featuredimage") }}
   <section class="featured-image">
     <a href="{{ .Permalink }}">
       <img src="{{ .Permalink | relURL }}{{ .Params.featuredimage }}" alt="">
     </a>
   </section>
{{ end }}

If a file named featured.jpg is found in the same directory as the article, the code above uses that image as the featured image. If featured.jpg is not present, fall back to using the image specified in the article’s front matter. The image link opens the article.

Second part:

Now, when the article is clicked and displayed using the single.html template, I want the featured image to be still shown, and it should link to itself. This way, when users click on the image, it will open the image in its full size.

I added this to single.html, similar logic:

{{ if .Resources.GetMatch "featured.*" }}
   <section class="featured-image">
      <a href="{{ (.Resources.GetMatch "featured.*").RelPermalink }}">
        <img src="{{ (.Resources.GetMatch "featured.*").RelPermalink }}">
      </a>
   </section>
   {{ else if (isset .Params "featuredimage") }}
   <section class="featured-image"></section>
      <a href="{{ .Permalink | relURL }}{{ .Params.featuredimage }}">
        <img src="{{ .Permalink | relURL }}{{ .Params.featuredimage }}" alt="">
      </a>
   </section>
 {{ end }}

What do you think about this method? I’m not sure about {{ .Permalink | relURL }}{{ .Params.featuredimage }} in single.html, although it does work.

Assuming this front matter…

featuredimage = 'foo.jpg'

…what is the path to the file from the root of your project? Is it:

  • In the same directory as the index.md file?
  • In the static directory?
  • In the assets directory?

Same directory as index.

+ content
       + posts
              + 2024
                     + 2024-08-10-first-post
                             - featured.jpg
                             - index.md

So your content might look like this?

content/
├── posts/
│   ├── post-1/
│   │   ├── featured.jpg
│   │   └── index.md
│   ├── post-2/
│   │   ├── foo.jpg
│   │   └── index.md  <-- front matter: featuredimage = 'foo.jpg'
│   └── _index.md
└── _index.md

Is that correct?

Yes, exactly.

You can simplify and make this less fragile with something like…

list.html

{{ range $p := .Pages }}
  <h2><a href="{{ .RelPermalink }}">{{ .LinkTitle }}</a></h2>
  {{ with or (.Resources.GetMatch "featured.*") (.Resources.Get .Params.featuredimage) }}
    <section class="featured-image">
      <a href="{{ $p.RelPermalink }}">
        <img src="{{ .RelPermalink }}">
      </a>
    </section>
  {{ end }}
{{ end }}

single.html

{{ with or (.Resources.GetMatch "featured.*") (.Resources.Get .Params.featuredimage) }}
  <section class="featured-image">
    <a href="{{ .RelPermalink }}">
      <img src="{{ .RelPermalink }}">
    </a>
  </section>
{{ end }}

I don’t see a need to use an absolute URL (i.e., .Permalink) anywhere, but you can obviously change that in the above as needed.

Thank you. I appreciate your time.

I used {{ range $p := .Paginator.Pages }} because I have pagination. Is that a problem? Here’s my full list.html:

{{ define "main" }}
<section class="blog">
  <div class="blog-container">
    <h2>{{ .Title }}</h2>
    {{ range $p := .Paginator.Pages }}
    <article class="blog-post">
      <header>
        <h3>
          <a href="{{ .RelPermalink }}">{{ .Title }}</a>
        </h3>
        <p>
          {{ with .Params.tags }}
            {{ range . }}
            <a class="tag" href="/tags/{{ . | urlize }}">{{ . }}</a>
            {{ end }}
          {{ end }}
        </p>
      </header>

      {{ with or (.Resources.GetMatch "featured.*") (.Resources.Get .Params.featuredimage) }}
      <section class="featured-image">
        <a href="{{ $p.RelPermalink }}">
          <img src="{{ .RelPermalink }}">
        </a>
      </section>
      {{ end }}
      
      <section class="post-summary">
        <p>{{ .Summary }}</p>
      </section>

      <footer>
        <p>
          {{- if .Truncated -}}
          <a class="read-more" href='{{ .RelPermalink }}'>Read more&hellip;</a>
          {{- end -}}
        </p>
      </footer>
    </article>
    {{ end }}
  </div>
</section>
{{ template "_internal/pagination.html" . }}
{{ end }}

No. $p gets a page reference either way.

1 Like

Your examples work great! I just added the width and height attributes to the images because, without them, there was a gap on the page while the images loaded, which was distracting. Also, omitting these attributes seemed to impact the website’s performance. I ran a check on Google PageSpeed Insights, and it recommended adding the width and height attributes to all my images to improve performance.

I added them, like so:

list.html:

{{ range $p := .Pages }}
  <h2><a href="{{ .RelPermalink }}">{{ .LinkTitle }}</a></h2>
  {{ with or (.Resources.GetMatch "featured.*") (.Resources.Get .Params.featuredimage) }}
    <section class="featured-image">
      <a href="{{ $p.RelPermalink }}">
        <img src="{{ .RelPermalink }}" width="{{ .Width }}" height="{{ .Height }}" alt="featuredimage">
      </a>
    </section>
  {{ end }}
{{ end }}

single.html

{{ with or (.Resources.GetMatch "featured.*") (.Resources.Get .Params.featuredimage) }}
  <section class="featured-image">
    <a href="{{ .RelPermalink }}">
      <img src="{{ .RelPermalink }}" width="{{ .Width }}" height="{{ .Height }}" alt="featuredimage">
    </a>
  </section>
{{ end }}

I hope I’ve implemented the Width and Height attributes correctly.

Now, I would like to obtain the width and height when using the figure shortcode as well. You mentioned at the beginning of this thread that the figure shortcode can be modified by overriding the original. I was wondering if there’s a way for the figure shortcode to automatically determine the Width and Height if they are not specified. Could I add an else condition to the with statement to handle this?

This is the original code of the figure shortcode:

<img src="{{ $src }}"
    {{- if or (.Get "alt") (.Get "caption") }}
    alt="{{ with .Get "alt" }}{{ . }}{{ else }}{{ .Get "caption" | markdownify| plainify }}{{ end }}"
    {{- end -}}
    {{- with .Get "width" }} width="{{ . }}"{{ end -}}
    {{- with .Get "height" }} height="{{ . }}"{{ end -}}
    {{- with .Get "loading" }} loading="{{ . }}"{{ end -}}
  ><!-- Closing img tag -->

I was thinking about something like this:

{{- with .Get "width" }} width="{{ . }} else {{ .Width }}" {{ end -}}