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.)