If you have a multi-language Hugo site, <link rel="" hreflang="" href="" />
is highly encouraged.
The code
In your baseof.html
or head.html
(or whichever filename your theme is using):
{{- if .IsTranslated -}}
{{ range .AllTranslations }}
<link rel="alternate" hreflang="{{ .Language.Lang }}" href="{{ .Permalink }}" />
{{- end -}}
{{ range first 1 .AllTranslations }}
<link rel="alternate" hreflang="x-default" href="{{ .Permalink }}" />
{{- end -}}
{{ end }}
In your sitemap.xml
file, add the following within <url></url>
{{- if .IsTranslated -}}
{{ range .AllTranslations }}
<xhtml:link rel="alternate" hreflang="{{ .Language.Lang }}" href="{{ .Permalink }}" />
{{- end -}}
{{ range first 1 .AllTranslations }}
<xhtml:link rel="alternate" hreflang="x-default" href="{{ .Permalink }}" />
{{- end -}}
{{ end }}
Example: This is my sitemap.xml
file (with additional edits):
{{ printf "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>" | safeHTML }}
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd" xmlns:xhtml="http://www.w3.org/1999/xhtml">
{{- range .Data.Pages -}}
{{ if .Permalink }}
<url>
<loc>{{ .Permalink }}</loc>
{{- if not .Lastmod.IsZero }}
<lastmod>{{ safeHTML ( .Lastmod.Format "2006-01-02T15:04:05Z" ) }}</lastmod>
{{- end -}}
{{- with .Sitemap.ChangeFreq }}
<changefreq>{{ . }}</changefreq>
{{- end -}}
{{- if ge .Sitemap.Priority 0.0 }}
<priority>{{ .Sitemap.Priority }}</priority>
{{- end -}}
{{- if .IsTranslated -}}
{{ range .AllTranslations }}
<xhtml:link rel="alternate" hreflang="{{ .Language.Lang }}" href="{{ .Permalink }}" />
{{- end -}}
{{ range first 1 .AllTranslations }}
<xhtml:link rel="alternate" hreflang="x-default" href="{{ .Permalink }}" />
{{- end -}}
{{ end }}
</url>
{{- end -}}
{{ end }}
</urlset>
BONUS: If you have a separate list.atom.xml
file instead of the default rss.xml
, add this as a child of <feed></feed>
or before <entry>
:
{{- with .OutputFormats.Get "Atom" }}
<link rel="self" type="{{ .MediaType.Type | html }}" hreflang="{{ $.Language.Lang }}" href="{{ .Permalink | safeURL }}" />
{{- end }}
{{- if .IsTranslated -}}
{{ range .Translations }}
<link rel="alternate" type="application/atom+xml" hreflang="{{ .Language.Lang }}" href="{{ .Permalink }}feed.xml" />
{{- end -}}
{{ range first 1 .AllTranslations }}
<link rel="alternate" type="application/atom+xml" hreflang="x-default" href="{{ .Permalink }}feed.xml" />
{{- end -}}
{{ end }}
{{- range .AlternativeOutputFormats }}
<link rel="alternate" type="{{ .MediaType.Type | html }}" hreflang="{{ $.Language.Lang }}" href="{{ .Permalink | safeURL }}" />
{{- end }}
See it in action:
-
Compare view-source The YOOki Chronicles and view-source YOOki 연대기
You should see the same:<link rel="alternate" hreflang="en-ph" href="https://im.YourOnly.One/yuki/" /> <link rel="alternate" hreflang="ja" href="https://im.YourOnly.One/yuki/ja/" /> <link rel="alternate" hreflang="ko" href="https://im.YourOnly.One/yuki/ko/" /> <link rel="alternate" hreflang="x-default" href="https://im.YourOnly.One/yuki/" />
-
Compare view-source Is a derivative of a Public Domain work, fanfiction? and view-source パブリックドメインの派生物は作品、ファンフィクションですか?
You should see the same:<link rel="alternate" hreflang="en-ph" href="https://im.YourOnly.One/yuki/derivative-public-domain-fanfiction-202197/" /> <link rel="alternate" hreflang="ja" href="https://im.YourOnly.One/yuki/ja/derivative-public-domain-fanfiction-202197/" /> <link rel="alternate" hreflang="ko" href="https://im.YourOnly.One/yuki/ko/derivative-public-domain-fanfiction-202197/" /> <link rel="alternate" hreflang="x-default" href="https://im.YourOnly.One/yuki/derivative-public-domain-fanfiction-202197/" />
Notes
-
x-default
will always point to the first language listed in yourconfig
file. Make sure the first language in yourconfig
file is your default/fallback language (usually English). - Atom feed: The
<link>
element is allowed in Atom 1.0 feeds. Personally, I do not know if there are readers and/or services that consume that extra data.- Do not add it in Hugo’s default
rss.xml
as that is a combined RSS2.0 and Atom1.0 template. RSS2 will reject the<link>
element and will produce an error. But again, I am not aware of any reader and/or service which consumes that extra information.
- Do not add it in Hugo’s default
- What is important is your HTML files and sitemap.
- In some sites they mention that
rel=canonical
must match one of thehreflang
values. It is not in Google’s official document, what their document emphasized on is that the current language must also show up in the hreflang list in that same article or page.- This makes sense because Google, and other search engines, also use
rel=canonical
for syndication sites. Syndication sites, for example news sites, display articles verbatim. Without arel=canonical
pointing to the original/source article the syndication site and/or the original site will be marked as “duplicate”. - In other words, there are cases wherein
rel=canonical
will be different than any of thehreflang
values listed. - Example document stating that
rel=canonical
must match one of thehreflang
values: Wikipedia: hreflang 2.4 Common Mistakes.
- This makes sense because Google, and other search engines, also use
More about hreflang
and x-default
, and rel=canonical
I hope it helped! Enjoy life!
Shalom!