Alias paths in v0.155.0 and later

Breaking change

Prior to v0.155.0, alias paths beginning with a slash (/) were treated as server-relative. In v0.155.0 and later, they are now site-relative. This change only affects multilingual single-host projects that used alias paths beginning with a slash (/) to cross language boundaries.

For example, if you have a file content/foo.en.md with the following front matter:

aliases:
  - /de/foo-in-german/

In v0.155.0 and later, this will resolve to /en/de/foo-in-german/ instead of /de/foo-in-german/.

The reason for the change

The short version is that the previous implementation was broken in several ways. It was also difficult to reason about in relation to the multidimensional content model introduced in v0.153.0. In this model, a site is no longer defined just by language, but by role and version as well.

To resolve this, we have changed how Hugo interprets alias paths. There are three types of relative URL paths in the context of a Hugo project:

Type Example Description
page-relative foo, ./foo, ../foo Relative to the current page
site-relative /foo Relative to the current site root (role, version, language)
server-relative /guest/v1.2.3/de/foo Relative to the web server host root

Prior to v0.155.0, alias paths beginning with a slash (/) were treated as server-relative. In v0.155.0 and later, they are now site-relative. This change ensures that aliases for all output formats work correctly across different roles, versions, and languages.

Handling missing translations

If you were previously using aliases as a workaround to handle missing translations, there is a much better way to do this now using a sites matrix.

For example, to set the English version of a page to provide the content for all other missing language translations, add this to your front matter:

title: p1
sites:
  matrix:
    languages: ['**']

You can also define a sites matrix in your site configuration or cascade it down to descendant pages.

Updated documentation

We have overhauled the documentation to help you navigate these changes:

3 Likes

Amazing work on the docs (and Hugo)! :raising_hands:t2: :tada: And thanks for this post.


Questions

Q. Is there a semantic distinction between foo and ./foo?

I’m asking because I’ve found it useful to distinguish between them for index pages, for example:

content/en/docs/collector/_index.md

---
title: Collector
description: Vendor-agnostic way to receive, process and export telemetry data.
aliases: [./about]
...
---

Is a shorthand for

---
title: Collector
description: Vendor-agnostic way to receive, process and export telemetry data.
aliases: [collector/about]
...
---

The interpretation of ./, helps keep the alias path DRY.

Is this the current Hugo interpretation. If not, can it be? :slight_smile:

Q. How about in-page links?

I think that the new interpretation of page- and site-relative alias paths is great. It certainly matches my expectations, based on the use case of the projects I work on.

Is the plan to support the same interpretation in the (default) link render hook?


Regarding:

languages: ['**']

Cool, I didn’t know we could write that.

Hugo interprets those paths in the same way that your OS does; there is no difference between foo and ./foo. Interpreting those differently would be confusing, unexpected, and a departure from the universal standards of path resolution.

To be clear, with this content structure:

content/
└── en/
    β”œβ”€β”€ docs/
    β”‚   β”œβ”€β”€ collector/
    β”‚   β”‚   └── _index.md
    β”‚   └── _index.md
    └── _index.md

And with this front matter in content/en/docs/collector/_index.md:

---
title: Collector
aliases: [about]
---

The published site looks like this:

public/
β”œβ”€β”€ de/
β”‚   └── index.html
β”œβ”€β”€ en/
β”‚   β”œβ”€β”€ docs/
β”‚   β”‚   β”œβ”€β”€ about/
β”‚   β”‚   β”‚   └── index.html
β”‚   β”‚   β”œβ”€β”€ collector/
β”‚   β”‚   β”‚   └── index.html
β”‚   β”‚   └── index.html
β”‚   └── index.html
└── index.html

With this front matter:

---
title: Collector
aliases: [./about]
---

The published site looks like this (same as above):

public/
β”œβ”€β”€ de/
β”‚   └── index.html
β”œβ”€β”€ en/
β”‚   β”œβ”€β”€ docs/
β”‚   β”‚   β”œβ”€β”€ about/
β”‚   β”‚   β”‚   └── index.html
β”‚   β”‚   β”œβ”€β”€ collector/
β”‚   β”‚   β”‚   └── index.html
β”‚   β”‚   └── index.html
β”‚   └── index.html
└── index.html

With this front matter:

---
title: Collector
aliases: [collector/about]
---

The published site looks like this:

public/
β”œβ”€β”€ de/
β”‚   └── index.html
β”œβ”€β”€ en/
β”‚   β”œβ”€β”€ docs/
β”‚   β”‚   β”œβ”€β”€ collector/
β”‚   β”‚   β”‚   β”œβ”€β”€ about/
β”‚   β”‚   β”‚   β”‚   └── index.html
β”‚   β”‚   β”‚   └── index.html
β”‚   β”‚   └── index.html
β”‚   └── index.html
└── index.html

Unless I’m missing something, we already do. Try it:

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

Very cool test project! Thanks for putting that together. Hmm, has markdown link resolution always worked like this? (I think not, but I might have been mistaken) Anyhow, I’m glad it works that way (now)!


You are right that for file systems, it’s natural for foo and ./foo to refer to the same entity. But that isn’t β€œuniversal”. Examples:

  • POSIX shells: foo searches $PATH; ./foo means β€œrun foo from current directory”.
  • Node.js: require('foo') resolves a package/core module; require('./foo') resolves a local file.
  • Go tooling: go test foo treats foo as an import path; go test ./foo means local directory ./foo.
  • Ruby: require 'foo' uses load path; require './foo' is explicit relative file.

So, in the context of an alias inside a Hugo index file, IMHO, it wouldn’t be unreasonable to interpret ./foo as being relative to the folder containing the index file. But I understand not wanting to interpret ./foo in that way.

Thanks!

If you are asking, β€œHave Hugo’s embedded link and render hooks always resolved link destinations like this?”, then the answer is yes.

1 Like

Wow, then I’ve had quite a misconception about this for a while! :sweat_smile: Thanks for helping me clarify that.