After Hugo update (0.88.1 -> 0.142.0), anchor links with # have a leading slash

I’m a technical writer at a software company and we use Hugo for our knowledge base. We used to have a web developer who could help us keep the site up to date, but at this point I’m the person who does that. I’d like to update our version of Hugo, which is currently at 0.88.1, to the latest version. I did this on a test branch of the project and corrected the build errors, but now a function of the site seems to be broken: Markdown links to IDs on the current page are now adding a / to the beginning, so they take you to the homepage with the anchor link.

So far I’ve tried:

  • Making sure the baseURL is set in config.yml
  • Making sure goldmark is turned on
  • Inspecting the page to see what the rendered HTML shows, which is indeed a /#anchor.

What am I missing? Is there something in recent versions that changed the behavior here? We have a lot of custom functions and scripts and things, so perhaps one of them is interfering, but I’m not quite sure how to start an investigation. Any help would be appreciated!

Is this a multilingual site?
Can you share your site configuration?

Yes, it is a multilingual site. Here’s my config with some redacted sections. I’ve included the comments that were in there from past updates as well.

## In `make build`, `make local-build` and `make local-watch`, the baseURL defined here gets overridden by the --baseURL parameter passed to the hugo command
baseURL: https://redacted.com/
publishDir: dist
module:
  mounts:
    - source: content
      target: content
    - source: layouts
      target: layouts
    - source: data
      target: data
    - source: assets
      target: assets
    - source: static
      target: static
    ## The mounts module doesn't pick up static/img as part of static/ so we declare it explicitly here
    ## Confirmed as of Hugo 0.80.0 that this explicit declaration is still necessary
    - source: static/img
      target: static/img
    ## Mount our @redacted/redacted-site-shared static files into Hugo's filesystem at /static
    - source: node_modules/@redacted
      target: static
    ## The mounts module doesn't seem to pick up  shared/static/img as part of shared/static so we declare it explicitly here
    ## Strangely, this appears to be specific to shared/static/img as the module picks up static/fonts, static/favicon.ico, and static/icons just fine
    - source: node_modules/@redacted
      target: static/img
    ## Mount our @redacted/redacted-site-shared assets files into Hugo's filesystem at /assets
    - source: node_modules/@redacted
      target: assets
theme: hugo
defaultContentLanguage: en
title: redacted
preserveTaxonomyNames: true
enableGitInfo: true
disableHugoGeneratorInject: true
params:
  mainURL: https://redacted.com
  blogURL: https://blog.redacted.com
  supportURL: ""
  i18nEnabled: true
themesDir: node_modules/@redacted/redacted-site-shared

Taxonomies:
  category: category
  topic: topic
  series: series

permalinks:
  article: /:filename/

ignoreFiles:
  - \.ts$

markup:
  goldmark:
    renderer:
      ## We set "unsafe: true" to allow embedding HTML in our Markdown in places where we need it (e.g. specific classes, cases where we can't/don't use shortcodes, etc.)
      unsafe: true

minify:
  tdewolff:
    html:
      keepWhitespace: false
      keepQuotes: false

Languages:
  en:
    weight: 1
    flag: ca
    languageName: English

  es:
    weight: 2
    title: Soporte técnico de redacted
    flag: es
    languageName: Español
    Taxonomies:
      category: category
      topic: topic
      series: series

  de:
    weight: 2
    title: redacted Support
    flag: de
    languageName: Deutsch
    Taxonomies:
      category: category
      topic: topic
      series: series

  fr:
    weight: 2
    title: Support Technique de redacted
    flag: fr
    languageName: Français
    Taxonomies:
      category: category
      topic: topic
      series: series

  jp:
    weight: 2
    title: redacted サポート
    flag: jp
    languageName: 日本語
    Taxonomies:
      category: category
      topic: topic
      series: series

  it:
    weight: 2
    title: Supporto redacted
    flag: it
    languageName: Italiano
    Taxonomies:
      category: category
      topic: topic
      series: series

  ru:
    weight: 2
    title: Поддержка redacted
    flag: ru
    languageName: Pусский
    Taxonomies:
      category: category
      topic: topic
      series: series

Nothing is jumping out at me. Let’s go the other direction. Here’s a multilingual site. Go to Post 1, in either language, and click on the link to section 3 on the same page.

git clone --single-branch -b hugo-forum-topic-53333 https://github.com/jmooring/hugo-testing hugo-forum-topic-53333
cd hugo-forum-topic-53333
hugo server

The above is a really simple site. Perhaps you can compare it to yours to determine what’s different.

Another question: do you have any files under layouts/_default/_markup, either in the root of your project or within your theme?

The header link works fine on the example repo you posted, so it definitely has something to do with mine, I just can’t seem to track down where the issue is. I don’t have any layouts/_default/_markup file or folder, just list.html and single.html in the _default folder.

I considered that it might be a script modifying things, but turning off JS in my browser and then opening the local server still gives me the same issue…

Okay I think I figured it out! I thought this issue was happening throughout pages, but it was specifically within several shortcodes that we use often. One is a table of contents, which has this as its layout:

<nav class="toc secondary-nav">
{{ .Inner | markdownify | safeHTML }}
</nav>

We then use it like this:

{{< toc >}}

* [One](#one)
* [Two](#two)
* [Three](#three)

{{< /toc >}}

It seems that the markdownify component changed in between versions, so that’s now adding the / in there. The best solution I could find was this:

{{ .Inner | markdownify | replaceRE `href="/#` `href="#` | safeHTML }}

I also need to do this for other shortcodes where we use markdownify since inner links will be broken there as well. Is there perhaps a better solution than the one I just showed?

Instead of playing the regex game, let’s try replacing markdownify with .Page.RenderString, then test again. And the .Page.RenderString method returns the template.HTML data type, so you don’t have to pass it though the safeHTML function. This should be sufficient:

{{ .Inner | .Page.RenderString  }}

Thank you, that does work! There’s one other template for a platform switcher functionality where that doesn’t work, though. Here’s the contents of the iOS one… I’m guessing it has to do with the variables set earlier on?

{{ $defaultOrder := split "mac,ios,windows,android" "," }}
{{ $platforms := default $defaultOrder .Parent.Params }}

{{ $id := or (.Page.Scratch.Get "id") 0 }}

{{ $content := .Inner }}

{{ range $k, $v := $platforms }}
    {{ if (eq $v "ios") }}
        <div id="tab-{{$id}}-{{$k}}" class="tab-content">
            {{ $content | markdownify | replaceRE `href="/#` `href="#` | safeHTML}}
        </div>
    {{ end }}
{{ end }}

And for the general platform.html:

{{ $id := or (.Page.Scratch.Get "id") 0 }}

{{ $names := dict "mac" "Mac" "windows" "Windows" "ios" "iOS" "android" "Android" "linux" "Linux" }}

{{ $defaultOrder := split "mac,ios,windows,android,linux" "," }}

{{ $platforms := default $defaultOrder .Params }}

<div class="platform-selector" role="tablist" aria-multiselectable="false">
    <ul class="platforms tabs">
        <p>{{ i18n "instructions-for" }}</p>
        {{ range $k, $v := $platforms }}
        <li class="{{$v}}" data-tab="tab-{{$id}}-{{$k}}">
            <a class="tab"><span>{{ index $names $v }}</span></a>
        </li>
        {{ end }}
    </ul>
    <div class="content">{{ .Inner }}</div>
</div>

{{ .Page.Scratch.Add "id" 1 }}

The first one looks like a shortcode.

Change this:

{{ range $k, $v := $platforms }}
    {{ if (eq $v "ios") }}
        <div id="tab-{{$id}}-{{$k}}" class="tab-content">
            {{ $content | markdownify | replaceRE `href="/#` `href="#` | safeHTML}}
        </div>
    {{ end }}
{{ end }}

To this:

{{ range $k, $v := $platforms }}
    {{ if (eq $v "ios") }}
        <div id="tab-{{$id}}-{{$k}}" class="tab-content">
            {{ $content | $.Page.RenderString }}
        </div>
    {{ end }}
{{ end }}

And test again.

At some point I hope we deprecate the markdownify function; it has issues. Use .Page.RenderString instead… everywhere.

Aha, there we go! Thank you so much. Glad we figured out what was going on.

1 Like

This topic was automatically closed 2 days after the last reply. New replies are no longer allowed.