You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@skywalking.apache.org by wu...@apache.org on 2026/07/31 00:22:56 UTC
(skywalking-website) 01/05: SEO: absolute baseURL, canonical, hreflang, lang and JSON-LD
This is an automated email from the ASF dual-hosted git repository.
wu-sheng pushed a commit to branch seo-foundations
in repository https://gitbox.apache.org/repos/asf/skywalking-website.git
commit 679f3d2170dbd39afe44b964050e1e21a62275cc
Author: Wu Sheng <wu...@foxmail.com>
AuthorDate: Thu Jul 30 23:34:38 2026 +0800
SEO: absolute baseURL, canonical, hreflang, lang and JSON-LD
The site emitted no rel=canonical anywhere and baseURL was "/", so Hugo
generated relative <loc> values in sitemap.xml. The sitemaps.org spec requires
an absolute URL there, which made all 4704 entries unusable for discovery, and
og:url resolved to "/" on the home page.
- config.toml: baseURL -> https://skywalking.apache.org/. canonifyURLs stays
off, so internal links are unchanged; only sitemap/og:url/canonical/RSS gain
absolute URLs.
- layouts/robots.txt: new, advertises the sitemap. Hugo's built-in default
emitted a bare "User-agent: *" with no Sitemap: directive.
- layouts/partials/seo/meta.html: rel=canonical on every page, a
<meta name="description"> where the page has real prose, and reciprocal
en/zh-CN hreflang on the 53 blog posts that exist in both languages.
- layouts/partials/seo/doc-canonical-map.html: docs.js renders `Latest` and the
newest tag from the same commitId, so e.g. /docs/main/latest/ and
/docs/main/v10.4.0/ are identical documents at two URLs. The tagged one now
canonicalises to /latest/. Keyed on commitId equality, so it self-corrects at
each release. Every version URL stays live and served; older versions
self-canonicalise and remain independently indexable.
- layouts/partials/seo/lang.html + all baseof templates: content/zh holds
Chinese blog posts but the site is a single Hugo language, so all 113 of them
declared lang="en". Derive the tag from the URL instead.
- layouts/partials/seo/schema.html: JSON-LD replacing the internal template's
bare itemprop microdata - Organization and SoftwareApplication on the home
page, BlogPosting on posts, TechArticle plus BreadcrumbList on docs.
- content/_index.html: add a description. Without one, Hugo's internal
opengraph template fell back to .Summary, which scraped the hero SVG
("- APACHE SOFTWARE FOUNDATION - OPEN SOURCE - ...") into every social card.
- data/docs.yml: BanyanDB `Latest` pointed at dda5b5ed, which is the v0.10.1 tag
commit, so /docs/skywalking-banyandb/latest/ served v0.10.1 docs through both
the v0.10.2 and v0.10.3 releases. Realigned to v0.10.3.
Verified against a full local build: 4551/4552 pages carry a canonical (the
only page without is the Vite 3d-map SPA shell, which bypasses Hugo layouts),
200 sampled version-canonical targets all resolve, 113/113 zh pages and 30
zh_tags pages report zh-CN with no English page mislabelled, and JSON-LD parses
on 391/391 of a random 400-page sample with no dangling @id references.
---
config.toml | 5 +-
content/_index.html | 4 +
data/docs.yml | 2 +-
layouts/blog/baseof.html | 2 +-
layouts/contributors/baseof.html | 2 +-
layouts/docs/baseof.html | 2 +-
layouts/downloads/baseof.html | 2 +-
layouts/events/baseof.html | 2 +-
layouts/partials/hooks/head-end.html | 9 +-
layouts/partials/seo/doc-canonical-map.html | 42 ++++++++++
layouts/partials/seo/lang.html | 13 +++
layouts/partials/seo/meta.html | 62 ++++++++++++++
layouts/partials/seo/schema.html | 114 ++++++++++++++++++++++++++
layouts/projectdoc/baseof.html | 2 +-
layouts/robots.txt | 4 +
layouts/tags/baseof.html | 2 +-
layouts/team/baseof.html | 2 +-
layouts/users/baseof.html | 2 +-
layouts/zh/baseof.html | 2 +-
themes/docsy/layouts/_default/baseof.html | 2 +-
themes/docsy/layouts/blog/baseof.html | 2 +-
themes/docsy/layouts/contributors/baseof.html | 2 +-
themes/docsy/layouts/docs/baseof.html | 2 +-
themes/docsy/layouts/downloads/baseof.html | 2 +-
themes/docsy/layouts/events/baseof.html | 2 +-
themes/docsy/layouts/projectDoc/baseof.html | 2 +-
themes/docsy/layouts/swagger/baseof.html | 2 +-
themes/docsy/layouts/taxonomy/baseof.html | 2 +-
themes/docsy/layouts/team/baseof.html | 2 +-
themes/docsy/layouts/users/baseof.html | 2 +-
themes/docsy/layouts/zh/baseof.html | 2 +-
31 files changed, 273 insertions(+), 26 deletions(-)
diff --git a/config.toml b/config.toml
index 703f08d5cea5..6ec7106b12c9 100644
--- a/config.toml
+++ b/config.toml
@@ -1,4 +1,7 @@
-baseURL = "/"
+# Must be the absolute production URL: Hugo derives <loc> in sitemap.xml, og:url,
+# rel=canonical and the RSS links from it. A relative baseURL emits relative
+# <loc> values, which the sitemaps.org spec rejects outright.
+baseURL = "https://skywalking.apache.org/"
title = "Apache SkyWalking"
enableRobotsTXT = true
diff --git a/content/_index.html b/content/_index.html
index 5a66436d4dc7..8b22e443620d 100644
--- a/content/_index.html
+++ b/content/_index.html
@@ -1,6 +1,10 @@
+++
title = "Apache SkyWalking"
linkTitle = "Apache SkyWalking"
+# Without an explicit description Hugo's internal opengraph/twitter templates fall
+# back to .Summary, which on this page scrapes the hero SVG ("· APACHE SOFTWARE
+# FOUNDATION · OPEN SOURCE · ...") into every social card and search snippet.
+description = "Apache SkyWalking is an open source observability platform for cloud native architectures. It collects, analyzes, aggregates and visualizes distributed traces, metrics, logs, profiling and alarms from services, containers and Kubernetes infrastructure, with auto-instrumentation agents for Java, Go, Python, Node.js, PHP, Rust, Ruby and .NET, plus eBPF-based profiling."
+++
<!-- ===== HERO + PRODUCT BENTO (merged) ===== -->
diff --git a/data/docs.yml b/data/docs.yml
index d56848f2a0f9..a2e52e65127a 100644
--- a/data/docs.yml
+++ b/data/docs.yml
@@ -368,7 +368,7 @@
link: /docs/skywalking-banyandb/next/readme/
- version: Latest
link: /docs/skywalking-banyandb/latest/readme/
- commitId: dda5b5edfda8d90996f37fcb713d8b1035739dd4
+ commitId: 951e00e85b47b46cdabbe3d14e5be4a02b50dd29
- version: v0.10.3
link: /docs/skywalking-banyandb/v0.10.3/readme/
commitId: 951e00e85b47b46cdabbe3d14e5be4a02b50dd29
diff --git a/layouts/blog/baseof.html b/layouts/blog/baseof.html
index de48a5468a3c..f67505d96085 100644
--- a/layouts/blog/baseof.html
+++ b/layouts/blog/baseof.html
@@ -1,5 +1,5 @@
<!doctype html>
-<html lang="{{ .Site.Language.Lang }}" class="no-js">
+<html lang="{{ partial "seo/lang.html" . }}" class="no-js">
<head>
{{ partial "head.html" . }}
<title>{{ if .IsHome }}{{ .Site.Title }}{{ else }}{{ with .Title }}{{ . }} | {{ end }}{{ .Site.Title }}{{ end }}</title>
diff --git a/layouts/contributors/baseof.html b/layouts/contributors/baseof.html
index 544931f6a340..b54cae85e3eb 100644
--- a/layouts/contributors/baseof.html
+++ b/layouts/contributors/baseof.html
@@ -1,5 +1,5 @@
<!doctype html>
-<html lang="{{ .Site.Language.Lang }}" class="no-js">
+<html lang="{{ partial "seo/lang.html" . }}" class="no-js">
<head>
{{ partial "head.html" . }}
<title>{{ if .IsHome }}{{ .Site.Title }}{{ else }}{{ with .Title }}{{ . }} | {{ end }}{{ .Site.Title }}{{ end }}</title>
diff --git a/layouts/docs/baseof.html b/layouts/docs/baseof.html
index 01f13b451e16..36b314c4eb18 100644
--- a/layouts/docs/baseof.html
+++ b/layouts/docs/baseof.html
@@ -1,6 +1,6 @@
{{- /* /docs landing — light card grid, driven by data/docs.yml + data/stars.yml */ -}}
<!doctype html>
-<html lang="{{ .Site.Language.Lang }}" class="no-js td-docs">
+<html lang="{{ partial "seo/lang.html" . }}" class="no-js td-docs">
<head>
{{ partial "head.html" . }}
<title>{{ if .IsHome }}{{ .Site.Title }}{{ else }}{{ with .Title }}{{ . }} | {{ end }}{{ .Site.Title }}{{ end }}</title>
diff --git a/layouts/downloads/baseof.html b/layouts/downloads/baseof.html
index 2e3d130e8fe6..2c428755c157 100644
--- a/layouts/downloads/baseof.html
+++ b/layouts/downloads/baseof.html
@@ -1,5 +1,5 @@
<!doctype html>
-<html lang="{{ .Site.Language.Lang }}" class="no-js">
+<html lang="{{ partial "seo/lang.html" . }}" class="no-js">
<head>
{{ partial "head.html" . }}
<title>{{ if .IsHome }}{{ .Site.Title }}{{ else }}{{ with .Title }}{{ . }} | {{ end }}{{ .Site.Title }}{{ end }}</title>
diff --git a/layouts/events/baseof.html b/layouts/events/baseof.html
index 5af1546e6434..68b244c9c6a5 100644
--- a/layouts/events/baseof.html
+++ b/layouts/events/baseof.html
@@ -1,5 +1,5 @@
<!doctype html>
-<html lang="{{ .Site.Language.Lang }}" class="no-js">
+<html lang="{{ partial "seo/lang.html" . }}" class="no-js">
<head>
{{ partial "head.html" . }}
<title>{{ if .IsHome }}{{ .Site.Title }}{{ else }}{{ with .Title }}{{ . }} | {{ end }}{{ .Site.Title }}{{ end }}</title>
diff --git a/layouts/partials/hooks/head-end.html b/layouts/partials/hooks/head-end.html
index 16bb11ada604..54ff8ffb0ffd 100644
--- a/layouts/partials/hooks/head-end.html
+++ b/layouts/partials/hooks/head-end.html
@@ -1,2 +1,7 @@
-{{- /* Intentionally empty: the site uses the theme's system font stack
- ($font-family-base) throughout — no third-party (Google) web fonts. */ -}}
+{{- /* No third-party (Google) web fonts: the site uses the theme's system font
+ stack ($font-family-base) throughout.
+
+ This hook runs at the end of themes/docsy/layouts/partials/head.html and is
+ where the site's SEO metadata is emitted, so the theme itself stays unforked. */ -}}
+{{ partial "seo/meta.html" . }}
+{{ partial "seo/schema.html" . }}
diff --git a/layouts/partials/seo/doc-canonical-map.html b/layouts/partials/seo/doc-canonical-map.html
new file mode 100644
index 000000000000..a93728c019d3
--- /dev/null
+++ b/layouts/partials/seo/doc-canonical-map.html
@@ -0,0 +1,42 @@
+{{- /* Returns a slice of {from, to} URL-prefix pairs for versioned docs that are
+ an exact re-rendering of the `latest` alias.
+
+ docs.js builds one URL tree per entry in data/docs.yml, so `Latest` and the
+ newest tagged version are rendered twice from the *same* commitId — e.g.
+ /docs/main/latest/ and /docs/main/v10.4.0/ have identical article bodies and
+ differ only in nav hrefs and the version <select>. Both URLs stay live and
+ served; the tagged one just points rel=canonical at /latest/ so the pair
+ stops competing in search results.
+
+ This is keyed on commitId equality, so it corrects itself at every release:
+ once `Latest` advances to a new commit, the old tag no longer matches and
+ falls back to self-canonical. Genuinely older versions never appear here —
+ they self-canonicalise and remain independently indexable. */ -}}
+{{- $pairs := slice -}}
+{{- range $group := site.Data.docs -}}
+ {{- with $group.list -}}
+ {{- range $item := . -}}
+ {{- with $item.docs -}}
+ {{- $latestPrefix := "" -}}
+ {{- $latestCommit := "" -}}
+ {{- range $d := . -}}
+ {{- if and (eq $d.version "Latest") (default "" $d.commitId) (hasPrefix (default "" $d.link) "/docs/") -}}
+ {{- $latestCommit = $d.commitId -}}
+ {{- $latestPrefix = strings.TrimSuffix "readme/" $d.link -}}
+ {{- end -}}
+ {{- end -}}
+ {{- if ne $latestPrefix "" -}}
+ {{- range $d := . -}}
+ {{- if and (ne $d.version "Latest") (eq (default "" $d.commitId) $latestCommit) (hasPrefix (default "" $d.link) "/docs/") -}}
+ {{- $from := strings.TrimSuffix "readme/" $d.link -}}
+ {{- if ne $from $latestPrefix -}}
+ {{- $pairs = $pairs | append (dict "from" $from "to" $latestPrefix) -}}
+ {{- end -}}
+ {{- end -}}
+ {{- end -}}
+ {{- end -}}
+ {{- end -}}
+ {{- end -}}
+ {{- end -}}
+{{- end -}}
+{{- return $pairs -}}
diff --git a/layouts/partials/seo/lang.html b/layouts/partials/seo/lang.html
new file mode 100644
index 000000000000..873663c59d6d
--- /dev/null
+++ b/layouts/partials/seo/lang.html
@@ -0,0 +1,13 @@
+{{- /* Returns the BCP-47 language tag for the current page.
+
+ The site is configured as a single Hugo language (English), so every
+ baseof template that writes `lang="{{ .Site.Language.Lang }}"` emits
+ lang="en" — including the Chinese posts under content/zh and their
+ zh_tags taxonomy. Search engines (Baidu especially) use that attribute
+ to decide what language a document is in, so derive it from the URL
+ instead until/unless the site moves to a real [languages] config. */ -}}
+{{- $lang := site.Language.Lang -}}
+{{- if or (hasPrefix .RelPermalink "/zh/") (hasPrefix .RelPermalink "/zh_tags/") -}}
+ {{- $lang = "zh-CN" -}}
+{{- end -}}
+{{- return $lang -}}
diff --git a/layouts/partials/seo/meta.html b/layouts/partials/seo/meta.html
new file mode 100644
index 000000000000..dd61a77b373c
--- /dev/null
+++ b/layouts/partials/seo/meta.html
@@ -0,0 +1,62 @@
+{{- $path := .RelPermalink -}}
+{{- $lang := partial "seo/lang.html" . -}}
+
+{{- /* --- rel=canonical -------------------------------------------------------
+ Self-canonical everywhere, except versioned doc trees that are a duplicate
+ rendering of /latest/ (see seo/doc-canonical-map.html), which point at the
+ /latest/ equivalent of the same page. */ -}}
+{{- $canonical := .Permalink -}}
+{{- range $p := partialCached "seo/doc-canonical-map.html" . -}}
+ {{- if hasPrefix $path $p.from -}}
+ {{- $canonical = absURL (printf "%s%s" $p.to (strings.TrimPrefix $p.from $path)) -}}
+ {{- end -}}
+{{- end -}}
+<link rel="canonical" href="{{ $canonical }}">
+
+{{- /* --- meta description ----------------------------------------------------
+ Hugo's internal opengraph template already emits og:description, but no
+ plain <meta name="description"> was ever rendered. Only emit one when the
+ page has a real description or usable prose — a duplicated boilerplate
+ description across 4k doc pages is worse than letting Google build its own
+ snippet from the content. */ -}}
+{{- $desc := default "" .Params.description -}}
+{{- if and (eq $desc "") (not .IsHome) .Content -}}
+ {{- $desc = .Summary | plainify | htmlUnescape | chomp -}}
+ {{- $desc = replaceRE `\s+` " " $desc | truncate 160 "…" -}}
+{{- end -}}
+{{- with $desc }}
+<meta name="description" content="{{ . }}">
+{{- end }}
+
+{{- /* --- hreflang ------------------------------------------------------------
+ content/zh holds Chinese translations of ~half the posts in content/blog,
+ matched by identical slug. Emit reciprocal alternates only when both pages
+ actually exist, so an unpaired post never claims a translation it lacks. */ -}}
+{{- $en := "" -}}
+{{- $zh := "" -}}
+{{- if hasPrefix $path "/blog/" -}}
+ {{- $slug := strings.TrimPrefix "/blog/" $path -}}
+ {{- if and (ne $slug "") (site.GetPage (printf "/zh/%s" $slug)) -}}
+ {{- $en = $path -}}
+ {{- $zh = printf "/zh/%s" $slug -}}
+ {{- end -}}
+{{- else if hasPrefix $path "/zh/" -}}
+ {{- $slug := strings.TrimPrefix "/zh/" $path -}}
+ {{- if and (ne $slug "") (site.GetPage (printf "/blog/%s" $slug)) -}}
+ {{- $zh = $path -}}
+ {{- $en = printf "/blog/%s" $slug -}}
+ {{- end -}}
+{{- end -}}
+{{- if and (ne $en "") (ne $zh "") }}
+<link rel="alternate" hreflang="en" href="{{ $en | absURL }}">
+<link rel="alternate" hreflang="zh-CN" href="{{ $zh | absURL }}">
+<link rel="alternate" hreflang="x-default" href="{{ $en | absURL }}">
+{{- end }}
+
+{{- /* Note: og:locale is emitted by Hugo's _internal/opengraph.html and is always
+ the site language ("en"), including on the Chinese pages. _internal
+ templates cannot be overridden, and a second og:locale here would just be a
+ conflicting duplicate — the first one wins in every parser. <html lang> and
+ the hreflang pair above are the signals search engines actually use, so this
+ is left alone; fixing it would mean replacing the internal OpenGraph call in
+ themes/docsy/layouts/partials/head.html with a local partial. */ -}}
diff --git a/layouts/partials/seo/schema.html b/layouts/partials/seo/schema.html
new file mode 100644
index 000000000000..7e6c72e3f049
--- /dev/null
+++ b/layouts/partials/seo/schema.html
@@ -0,0 +1,114 @@
+{{- /* JSON-LD structured data.
+
+ Hugo's internal _internal/schema.html only emits a handful of bare itemprop
+ attributes, which neither Google's rich results nor LLM retrievers make much
+ use of. Emit real JSON-LD instead: Organization/SoftwareApplication on the
+ home page, BlogPosting on posts and release events, TechArticle plus a
+ BreadcrumbList on documentation pages. */ -}}
+
+{{- $lang := partial "seo/lang.html" . -}}
+{{- $org := dict
+ "@type" "Organization"
+ "@id" (absURL "#organization")
+ "name" "Apache SkyWalking"
+ "url" (absURL "")
+ "logo" (absURL "images/skywalking_400x400.png")
+ "sameAs" (slice
+ "https://github.com/apache/skywalking"
+ "https://twitter.com/asfskywalking"
+ "https://projects.apache.org/project.html?skywalking")
+ "parentOrganization" (dict
+ "@type" "Organization"
+ "name" "The Apache Software Foundation"
+ "url" "https://www.apache.org/")
+-}}
+{{- $publisher := dict "@id" (absURL "#organization") -}}
+
+{{- /* The Organization node is repeated in every page's @graph rather than only on
+ the home page: a bare "@id" reference to a node defined on a *different*
+ document is technically legal JSON-LD but consumers do not dereference it, so
+ publisher/author would resolve to nothing. */ -}}
+{{- $nodes := slice -}}
+
+{{- if .IsHome -}}
+ {{- /* Latest SkyWalking APM version, read defensively from data/releases.yml so a
+ reshuffle of that file can never fail the build. */ -}}
+ {{- $version := "" -}}
+ {{- with site.Data.releases -}}
+ {{- with index . 0 -}}
+ {{- with .list -}}
+ {{- with index . 0 -}}
+ {{- with .distribution -}}
+ {{- with index . 0 -}}
+ {{- $version = default "" .version -}}
+ {{- end -}}
+ {{- end -}}
+ {{- end -}}
+ {{- end -}}
+ {{- end -}}
+ {{- end -}}
+ {{- $app := dict
+ "@type" "SoftwareApplication"
+ "name" "Apache SkyWalking"
+ "url" (absURL "")
+ "applicationCategory" "DeveloperApplication"
+ "applicationSubCategory" "Observability, Application Performance Monitoring, Distributed Tracing"
+ "operatingSystem" "Linux, macOS, Windows"
+ "description" site.Params.description
+ "downloadUrl" (absURL "downloads/")
+ "license" "https://www.apache.org/licenses/LICENSE-2.0"
+ "isAccessibleForFree" true
+ "author" $publisher
+ "offers" (dict "@type" "Offer" "price" "0" "priceCurrency" "USD")
+ -}}
+ {{- with $version -}}
+ {{- $app = merge $app (dict "softwareVersion" (strings.TrimPrefix "v" .)) -}}
+ {{- end -}}
+ {{- $nodes = $nodes | append $org -}}
+ {{- $nodes = $nodes | append (dict
+ "@type" "WebSite"
+ "@id" (absURL "#website")
+ "url" (absURL "")
+ "name" site.Title
+ "inLanguage" $lang
+ "publisher" $publisher) -}}
+ {{- $nodes = $nodes | append $app -}}
+{{- else if eq .Kind "page" -}}
+ {{- $isDoc := hasPrefix .RelPermalink "/docs/" -}}
+ {{- $type := cond $isDoc "TechArticle" "BlogPosting" -}}
+ {{- $article := dict
+ "@type" $type
+ "headline" .Title
+ "url" .Permalink
+ "mainEntityOfPage" (dict "@type" "WebPage" "@id" .Permalink)
+ "inLanguage" $lang
+ "publisher" $publisher
+ -}}
+ {{- with .Params.description -}}
+ {{- $article = merge $article (dict "description" .) -}}
+ {{- end -}}
+ {{- if not .Date.IsZero -}}
+ {{- $article = merge $article (dict "datePublished" (.Date.Format "2006-01-02")) -}}
+ {{- end -}}
+ {{- if not .Lastmod.IsZero -}}
+ {{- $article = merge $article (dict "dateModified" (.Lastmod.Format "2006-01-02")) -}}
+ {{- end -}}
+ {{- with .Params.author -}}
+ {{- $article = merge $article (dict "author" (dict "@type" "Person" "name" .)) -}}
+ {{- end -}}
+ {{- $nodes = $nodes | append $org -}}
+ {{- $nodes = $nodes | append $article -}}
+
+ {{- if $isDoc -}}
+ {{- $nodes = $nodes | append (dict
+ "@type" "BreadcrumbList"
+ "itemListElement" (slice
+ (dict "@type" "ListItem" "position" 1 "name" site.Title "item" (absURL ""))
+ (dict "@type" "ListItem" "position" 2 "name" "Projects and Docs" "item" (absURL "docs/"))
+ (dict "@type" "ListItem" "position" 3 "name" .Title "item" .Permalink))) -}}
+ {{- end -}}
+{{- end -}}
+
+{{- with $nodes }}
+<script type="application/ld+json">{{ (dict "@context" "https://schema.org" "@graph" .) | jsonify | safeJS }}</script>
+{{- end }}
diff --git a/layouts/projectdoc/baseof.html b/layouts/projectdoc/baseof.html
index 438ba22da1e4..2fccb1d25f0f 100644
--- a/layouts/projectdoc/baseof.html
+++ b/layouts/projectdoc/baseof.html
@@ -1,5 +1,5 @@
<!doctype html>
-<html lang="{{ .Site.Language.Lang }}" class="no-js">
+<html lang="{{ partial "seo/lang.html" . }}" class="no-js">
<head>
{{ partial "head.html" . }}
<title>{{ if .IsHome }}{{ .Site.Title }}{{ else }}{{ with .Title }}{{ . }} | {{ end }}{{ .Site.Title }}{{ end }}</title>
diff --git a/layouts/robots.txt b/layouts/robots.txt
new file mode 100644
index 000000000000..6b4c001c43e1
--- /dev/null
+++ b/layouts/robots.txt
@@ -0,0 +1,4 @@
+User-agent: *
+Allow: /
+
+Sitemap: {{ "sitemap.xml" | absURL }}
diff --git a/layouts/tags/baseof.html b/layouts/tags/baseof.html
index 0102f4126662..f59e5a32e126 100644
--- a/layouts/tags/baseof.html
+++ b/layouts/tags/baseof.html
@@ -1,5 +1,5 @@
<!doctype html>
-<html lang="{{ .Site.Language.Lang }}" class="no-js">
+<html lang="{{ partial "seo/lang.html" . }}" class="no-js">
<head>
{{ partial "head.html" . }}
<title>{{ with .Title }}{{ . }} | {{ end }}{{ .Site.Title }}</title>
diff --git a/layouts/team/baseof.html b/layouts/team/baseof.html
index 2e1da8ac9231..da28a531ae1c 100644
--- a/layouts/team/baseof.html
+++ b/layouts/team/baseof.html
@@ -1,5 +1,5 @@
<!doctype html>
-<html lang="{{ .Site.Language.Lang }}" class="no-js">
+<html lang="{{ partial "seo/lang.html" . }}" class="no-js">
<head>
{{ partial "head.html" . }}
<title>{{ if .IsHome }}{{ .Site.Title }}{{ else }}{{ with .Title }}{{ . }} | {{ end }}{{ .Site.Title }}{{ end }}</title>
diff --git a/layouts/users/baseof.html b/layouts/users/baseof.html
index d40fb03fa29d..ecd09670b778 100644
--- a/layouts/users/baseof.html
+++ b/layouts/users/baseof.html
@@ -1,5 +1,5 @@
<!doctype html>
-<html lang="{{ .Site.Language.Lang }}" class="no-js">
+<html lang="{{ partial "seo/lang.html" . }}" class="no-js">
<head>
{{ partial "head.html" . }}
<title>{{ if .IsHome }}{{ .Site.Title }}{{ else }}{{ with .Title }}{{ . }} | {{ end }}{{ .Site.Title }}{{ end }}</title>
diff --git a/layouts/zh/baseof.html b/layouts/zh/baseof.html
index 0f4a9b9a87b0..5a98c3a863ce 100644
--- a/layouts/zh/baseof.html
+++ b/layouts/zh/baseof.html
@@ -1,5 +1,5 @@
<!doctype html>
-<html lang="{{ .Site.Language.Lang }}" class="no-js">
+<html lang="{{ partial "seo/lang.html" . }}" class="no-js">
<head>
{{ partial "head.html" . }}
<title>{{ if .IsHome }}{{ .Site.Title }}{{ else }}{{ with .Title }}{{ . }} | {{ end }}{{ .Site.Title }}{{ end }}</title>
diff --git a/themes/docsy/layouts/_default/baseof.html b/themes/docsy/layouts/_default/baseof.html
index 3775b1228853..93132b22d412 100644
--- a/themes/docsy/layouts/_default/baseof.html
+++ b/themes/docsy/layouts/_default/baseof.html
@@ -1,5 +1,5 @@
<!doctype html>
-<html lang="{{ .Site.Language.Lang }}" class="no-js _default">
+<html lang="{{ partial "seo/lang.html" . }}" class="no-js _default">
<head>
{{ partial "head.html" . }}
</head>
diff --git a/themes/docsy/layouts/blog/baseof.html b/themes/docsy/layouts/blog/baseof.html
index 361cc0a1ad5e..b6db5a95c9bb 100644
--- a/themes/docsy/layouts/blog/baseof.html
+++ b/themes/docsy/layouts/blog/baseof.html
@@ -1,5 +1,5 @@
<!doctype html>
-<html lang="{{ .Site.Language.Lang }}" class="no-js">
+<html lang="{{ partial "seo/lang.html" . }}" class="no-js">
<head>
{{ partial "head.html" . }}
<title>{{ if .IsHome }}{{ .Site.Title }}{{ else }}{{ with .Title }}{{ . }} | {{ end }}{{ .Site.Title }}{{ end }}</title>
diff --git a/themes/docsy/layouts/contributors/baseof.html b/themes/docsy/layouts/contributors/baseof.html
index a74f0f4c7286..45f9b30b04cf 100644
--- a/themes/docsy/layouts/contributors/baseof.html
+++ b/themes/docsy/layouts/contributors/baseof.html
@@ -1,5 +1,5 @@
<!doctype html>
-<html lang="{{ .Site.Language.Lang }}" class="no-js">
+<html lang="{{ partial "seo/lang.html" . }}" class="no-js">
<head>
{{ partial "head.html" . }}
<title>{{ if .IsHome }}{{ .Site.Title }}{{ else }}{{ with .Title }}{{ . }} | {{ end }}{{ .Site.Title }}{{ end }}</title>
diff --git a/themes/docsy/layouts/docs/baseof.html b/themes/docsy/layouts/docs/baseof.html
index ad75fde8ec7f..2b1b76bc309a 100644
--- a/themes/docsy/layouts/docs/baseof.html
+++ b/themes/docsy/layouts/docs/baseof.html
@@ -1,5 +1,5 @@
<!doctype html>
-<html lang="{{ .Site.Language.Lang }}" class="no-js td-docs">
+<html lang="{{ partial "seo/lang.html" . }}" class="no-js td-docs">
<head>
{{ partial "head.html" . }}
<title>{{ if .IsHome }}{{ .Site.Title }}{{ else }}{{ with .Title }}{{ . }} | {{ end }}{{ .Site.Title }}{{ end }}</title>
diff --git a/themes/docsy/layouts/downloads/baseof.html b/themes/docsy/layouts/downloads/baseof.html
index 7afdae42540e..0f6f15f16aa7 100644
--- a/themes/docsy/layouts/downloads/baseof.html
+++ b/themes/docsy/layouts/downloads/baseof.html
@@ -1,5 +1,5 @@
<!doctype html>
-<html lang="{{ .Site.Language.Lang }}" class="no-js">
+<html lang="{{ partial "seo/lang.html" . }}" class="no-js">
<head>
{{ partial "head.html" . }}
<title>{{ if .IsHome }}{{ .Site.Title }}{{ else }}{{ with .Title }}{{ . }} | {{ end }}{{ .Site.Title }}{{ end }}</title>
diff --git a/themes/docsy/layouts/events/baseof.html b/themes/docsy/layouts/events/baseof.html
index 48e02de277ff..b5af34091a92 100644
--- a/themes/docsy/layouts/events/baseof.html
+++ b/themes/docsy/layouts/events/baseof.html
@@ -1,5 +1,5 @@
<!doctype html>
-<html lang="{{ .Site.Language.Lang }}" class="no-js">
+<html lang="{{ partial "seo/lang.html" . }}" class="no-js">
<head>
{{ partial "head.html" . }}
<title>{{ if .IsHome }}{{ .Site.Title }}{{ else }}{{ with .Title }}{{ . }} | {{ end }}{{ .Site.Title }}{{ end }}</title>
diff --git a/themes/docsy/layouts/projectDoc/baseof.html b/themes/docsy/layouts/projectDoc/baseof.html
index 0844fc5e8ebf..57cf4a435ab7 100644
--- a/themes/docsy/layouts/projectDoc/baseof.html
+++ b/themes/docsy/layouts/projectDoc/baseof.html
@@ -1,5 +1,5 @@
<!doctype html>
-<html lang="{{ .Site.Language.Lang }}" class="no-js">
+<html lang="{{ partial "seo/lang.html" . }}" class="no-js">
<head>
{{ partial "head.html" . }}
<title>{{ if .IsHome }}{{ .Site.Title }}{{ else }}{{ with .Title }}{{ . }} | {{ end }}{{ .Site.Title }}{{ end }}</title>
diff --git a/themes/docsy/layouts/swagger/baseof.html b/themes/docsy/layouts/swagger/baseof.html
index 160ef0ca1e2e..70ca42452213 100644
--- a/themes/docsy/layouts/swagger/baseof.html
+++ b/themes/docsy/layouts/swagger/baseof.html
@@ -1,5 +1,5 @@
<!doctype html>
-<html lang="{{ .Site.Language.Lang }}" class="no-js">
+<html lang="{{ partial "seo/lang.html" . }}" class="no-js">
<head>
{{ partial "head.html" . }}
<title>{{ if .IsHome }}{{ .Site.Title }}{{ else }}{{ with .Title }}{{ . }} | {{ end }}{{ .Site.Title }}{{ end }}</title>
diff --git a/themes/docsy/layouts/taxonomy/baseof.html b/themes/docsy/layouts/taxonomy/baseof.html
index 9d2a7f34b773..0548c6ecd644 100644
--- a/themes/docsy/layouts/taxonomy/baseof.html
+++ b/themes/docsy/layouts/taxonomy/baseof.html
@@ -1,5 +1,5 @@
<!doctype html>
-<html lang="{{ .Site.Language.Lang }}" class="no-js">
+<html lang="{{ partial "seo/lang.html" . }}" class="no-js">
<head>
{{ partial "head.html" . }}
<title>{{ if .IsHome }}{{ .Site.Title }}{{ else }}{{ with .Title }}{{ . }} | {{ end }}{{ .Site.Title }}{{ end }}</title>
diff --git a/themes/docsy/layouts/team/baseof.html b/themes/docsy/layouts/team/baseof.html
index b711345c903a..3813e68eb3c3 100644
--- a/themes/docsy/layouts/team/baseof.html
+++ b/themes/docsy/layouts/team/baseof.html
@@ -1,5 +1,5 @@
<!doctype html>
-<html lang="{{ .Site.Language.Lang }}" class="no-js">
+<html lang="{{ partial "seo/lang.html" . }}" class="no-js">
<head>
{{ partial "head.html" . }}
<title>{{ if .IsHome }}{{ .Site.Title }}{{ else }}{{ with .Title }}{{ . }} | {{ end }}{{ .Site.Title }}{{ end }}</title>
diff --git a/themes/docsy/layouts/users/baseof.html b/themes/docsy/layouts/users/baseof.html
index cf3f937e0e74..ca821407ce64 100644
--- a/themes/docsy/layouts/users/baseof.html
+++ b/themes/docsy/layouts/users/baseof.html
@@ -1,5 +1,5 @@
<!doctype html>
-<html lang="{{ .Site.Language.Lang }}" class="no-js">
+<html lang="{{ partial "seo/lang.html" . }}" class="no-js">
<head>
{{ partial "head.html" . }}
<title>{{ if .IsHome }}{{ .Site.Title }}{{ else }}{{ with .Title }}{{ . }} | {{ end }}{{ .Site.Title }}{{ end }}</title>
diff --git a/themes/docsy/layouts/zh/baseof.html b/themes/docsy/layouts/zh/baseof.html
index bf9b31587607..e83be393626c 100644
--- a/themes/docsy/layouts/zh/baseof.html
+++ b/themes/docsy/layouts/zh/baseof.html
@@ -1,5 +1,5 @@
<!doctype html>
-<html lang="{{ .Site.Language.Lang }}" class="no-js">
+<html lang="{{ partial "seo/lang.html" . }}" class="no-js">
<head>
{{ partial "head.html" . }}
<title>{{ if .IsHome }}{{ .Site.Title }}{{ else }}{{ with .Title }}{{ . }} | {{ end }}{{ .Site.Title }}{{ end }}</title>