Hreflang implementation is one of the most technically challenging aspects of international SEO. This developer-focused guide covers implementation methods, validation, debugging, and automation.
The Three Rules of Hreflang
- Bidirectionality: If page A references page B, page B must reference page A
- Self-reference: Each page must include a hreflang tag pointing to itself
- Completeness: Every language version must reference all other versions including itself
Implementation Methods
Method 1: HTML Link Tags
<head>
<link rel="alternate" hreflang="en" href="https://example.com/en/" />
<link rel="alternate" hreflang="de" href="https://example.com/de/" />
<link rel="alternate" hreflang="fr" href="https://example.com/fr/" />
<link rel="alternate" hreflang="x-default" href="https://example.com/" />
</head>
Method 2: XML Sitemap
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:xhtml="http://www.w3.org/1999/xhtml">
<url>
<loc>https://example.com/en/product-a</loc>
<xhtml:link rel="alternate" hreflang="en" href="https://example.com/en/product-a" />
<xhtml:link rel="alternate" hreflang="de" href="https://example.com/de/produkt-a" />
<xhtml:link rel="alternate" hreflang="x-default" href="https://example.com/en/product-a" />
</url>
</urlset>
Method 3: HTTP Headers
HTTP/1.1 200 OK
Link: <https://example.com/en/>; rel="alternate"; hreflang="en",
<https://example.com/de/>; rel="alternate"; hreflang="de",
<https://example.com/>; rel="alternate"; hreflang="x-default"
Automated Hreflang Generation in Node.js
function generateHreflang(baseUrl, locales, currentPath) {
const hreflangTags = locales.map(locale => {
const href = `${baseUrl}/${locale}${currentPath}`;
return `<link rel="alternate" hreflang="${locale}" href="${href}" />`;
});
hreflangTags.push(
`<link rel="alternate" hreflang="x-default" href="${baseUrl}/en${currentPath}" />`
);
return hreflangTags.join("\n ");
}
Hreflang Validation Script
import requests
from bs4 import BeautifulSoup
import json
class HreflangValidator:
def __init__(self, base_url, locales):
self.base_url = base_url
self.locales = locales
self.issues = []
def validate_page(self, url):
try:
resp = requests.get(url, timeout=15)
soup = BeautifulSoup(resp.text, "html.parser")
hreflang_tags = []
for link in soup.find_all("link", rel="alternate"):
hreflang = link.get("hreflang")
href = link.get("href")
if hreflang:
hreflang_tags.append({"lang": hreflang, "href": href})
return self._check_issues(url, hreflang_tags)
except Exception as e:
self.issues.append({"url": url, "error": str(e)})
return False
def _check_issues(self, url, tags):
langs = {t["lang"] for t in tags}
if "x-default" not in langs:
self.issues.append({"url": url, "issue": "Missing x-default"})
for tag in tags:
if tag["href"] and not tag["href"].startswith("http"):
self.issues.append({"url": url, "issue": "Relative URL in hreflang"})
return len(self.issues) == 0
Conclusion
Flawless hreflang implementation requires a systematic approach: choose the right method, automate generation, and validate regularly.