Links

Not sure about you guys but creating links in Hugo, both in layouts and in contents, is excruciatingly painful especially when you implement multilingual mode. Not only the existing absLangURL and absURL broke the baseURL, they don't even have shortcodes for you to link them in your content posts.

These had made me came to a point to roll out my own type of absLangLink and absLink to replace both of them. This guide shows how to create these 2 shortcodes and partials to use across your project.

Why Not Relative Link

Security reason, as in, the final output working link and SEO budget. Relative link is usually useful when you are constructing your contents. Then, you pass it to Hugo to convert the relative link into an absolute link.

Long story short:

1. Relative link is for you to construct the absolute link in your website file generations, like Hugo or .htpaccess file for Apache or Nginx servers.
2. The final output that your viewer gets is always an absolute link. 

After all, that's Hugo's job to do the automatic conversion.

Exception

If you're building a multi-front website with Hugo. For those cases, you need to set your baseURL to "" instead of having any values.

Stage 1 - Using the Shortcode

Start with the end in mind: designing the call for shortcode. Since we're replacing the existing absLangURL and absURL, we should use absLangLink and absLink instead. In our content posts, we want to use it as such:

{{< absLangLink "/posts/first-page" >}}
{{< absLink "/img/banner.png" >}}

Now, in case you need to use absolute link (as in hotlinking to other pages), chances are you will be doing this in your Markdown:

[link](https://www.example.com)

Hence, we don't need to complicate things further.

Stage 2 - Building the Short Codes

Now that we have our short codes available, it's time to create the render files. Following the Short Codes guide, we should create 2 files in the layouts/shortcodes/ directory. However, as a standard practice, these render files should call the corresponding partial layout files, making the components available to layouts department too. Hence, these files are created.

layouts/shortcodes/absLangLink.html

{{ partial "links/absLangLink" (dict "context" . "url" (.Get 0)) }}


layouts/shortcodes/absLink.html

{{ partial "links/absLink" (dict "context" . "url" (.Get 0)) }}

Stage 3 - Design the Partials

Thanks to Hugo, you can perform some lightweight programming inside the HTML template. In our case, we have a dictionary values passing in as a single parameter (due to Go's HTML/template limitation). We can proceed to design our partials.

Using Partials in Layout

The above already demonstrates how to use the partials in the layouts department. All you need to do is:

{{ partial "links/absLangLink" (dict "context" . "url" "/posts/first-page") }}
{{ partial "links/absLink" (dict "context" . "url" "/img/banner.png") }}

That's it. We can proceed to build the partial contents. Based on the guide, we should create 2 files in the layouts/partials directory.


layouts/partials/links/absLangLink.html

Building the absolute language link is a bit relaxing since there is not much of checking.

The most important part would be detecting the former has a trailing slash and then act to remove the latter's leading slash for avoiding double slashes during fusing. For absLangLink, there 2 stages:

  • Fusing between BaseURL and current language code (called $base)
  • Fusing between $base and the given URL (called $url)

Also, Hugo has a habit to pass language prefix via the given relative URL so you need to trim it off. Lastly, printf the final URL.

Another thing to remember is to have all the dashes at the double braces ( {{- and -}} ). This is because we are generating a pure URL value so we don't want any whitespace before and after.

{{- $url := .url -}}
{{- $base := .context.Site.BaseURL -}}

{{- if strings.HasSuffix $base "/" -}}
        {{- $base = printf "%s%s" $base .context.Site.Language.Lang -}}
{{- else -}}
        {{- $base = printf "%s/%s" $base .context.Site.Language.Lang -}}
{{- end -}}

{{- if (strings.HasPrefix $url .context.Site.LanguagePrefix ) -}}
        {{- $url = strings.TrimPrefix .context.Site.LanguagePrefix $url -}}
{{- end -}}
{{- if strings.HasPrefix $url "/" -}}
        {{ $url = strings.TrimPrefix "/" $url -}}
{{- end -}}

{{- if strings.HasSuffix "$base" "/" -}}
        {{- printf "%s%s" $base $url -}}
{{- else -}}
        {{- printf "%s/%s" $base $url -}}
{{- end -}}


layouts/partials/links/absLink.html

Same case as absLangLink but this one has a stricter checking. In layouts department, chances are user may feed an absolute link like external CSS and Javascript CDN link. Hence, we need to perform additional URL Scheme checking before constructing the URL. Luckily, you only need to fuse once which is the $base and the $url after the URL.Scheme checking.

If there is an available URL.Scheme, we print out the value as it is and get out. Otherwise, we just strip the language prefix and construct the URL.

{{- $base := .context.Site.BaseURL -}}
{{- $url := .url -}}
{{- $check := urls.Parse $url -}}

{{- if ne $check.Scheme "" -}}
        {{- printf "%s" $url -}}
{{- else -}}
        {{- if strings.HasPrefix $url .context.Site.LanguagePrefix -}}
                {{- $url = strings.TrimPrefix .context.Site.LanguagePrefix $url -}}
        {{- end -}}

        {{- if strings.HasPrefix $url "/" -}}
                {{- $url = strings.TrimPrefix "/" $url -}}
        {{- end -}}

        {{- if strings.HasSuffix $base "/" -}}
                {{- printf "%s%s" $base $url -}}
        {{- else -}}
                {{- printf "%s/%s" $base $url -}}
        {{- end -}}
{{- end -}}

That's all about Hugo (IMHO, broken) URL system. Hopefully, the development team picks this up and make Hugo a greater product! (It's still the best compared it any of its competitors.)