Optimizing SEO for Multilingual Sites (hreflang Audit)
Multilingual site without proper hreflang is a systematic SEO problem. Google shows Russian version to Germans, English page ranks in Ukraine instead of Ukrainian, duplicate translations waste crawl budget. Hreflang tells search engines: "for users with language X and region Y use this page version."
Hreflang Syntax
<link rel="alternate" hreflang="ru" href="https://example.com/ru/about/" />
<link rel="alternate" hreflang="en" href="https://example.com/en/about/" />
<link rel="alternate" hreflang="de" href="https://example.com/de/about/" />
<link rel="alternate" hreflang="uk" href="https://example.com/uk/about/" />
<link rel="alternate" hreflang="x-default" href="https://example.com/about/" />
Three placement options:
-
<head>of each page - HTTP
Link:header (for PDF, non-HTML) - XML Sitemap
Critical rule: hreflang must be mutual. If page A links to B via hreflang, B must link back to A.
Common Mistakes
1. Missing self-reference. Each page must contain hreflang to itself:
<!-- On /ru/about/ -->
<link rel="alternate" hreflang="ru" href="https://example.com/ru/about/" />
<!-- + links to all other language versions -->
2. Wrong language codes. Use ISO 639-1 (language) + ISO 3166-1 alpha-2 (region):
ru → Russian (no region)
ru-RU → Russian, Russia
uk → Ukrainian
en-US → English, USA
en-GB → English, UK
3. Mixing www/non-www, http/https. Must match canonical version.
4. hreflang on noindex pages. Google recommends against.
Audit: Detection
Screaming Frog: Crawl → Reports → Hreflang → check for missing return links and wrong codes.
Python validation script:
import requests
from bs4 import BeautifulSoup
from collections import defaultdict
def fetch_hreflang(url: str) -> dict:
try:
resp = requests.get(url, timeout=10)
soup = BeautifulSoup(resp.text, 'html.parser')
links = soup.find_all('link', rel='alternate')
return {
link.get('hreflang'): link.get('href')
for link in links
if link.get('hreflang')
}
except:
return {}
def audit_hreflang(urls: list[str]) -> list[dict]:
hreflang_map = {}
errors = []
for url in urls:
hreflang_map[url] = fetch_hreflang(url)
for url, alternates in hreflang_map.items():
for lang, alt_url in alternates.items():
if lang == 'x-default':
continue
if alt_url not in hreflang_map:
errors.append({
'type': 'missing_page',
'source': url,
'target': alt_url
})
continue
target_alternates = hreflang_map.get(alt_url, {})
back_links = [v for v in target_alternates.values() if v == url]
if not back_links:
errors.append({
'type': 'missing_return_link',
'source': url,
'target': alt_url
})
return errors
GSC check: Search Console → International Targeting → Language shows hreflang errors found by Googlebot.
Hreflang in Sitemap
For large sites (1000+ pages), maintaining hreflang in every <head> is complex. Use sitemap:
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:xhtml="http://www.w3.org/1999/xhtml">
<url>
<loc>https://example.com/ru/about/</loc>
<xhtml:link rel="alternate" hreflang="ru" href="https://example.com/ru/about/"/>
<xhtml:link rel="alternate" hreflang="en" href="https://example.com/en/about/"/>
<xhtml:link rel="alternate" hreflang="uk" href="https://example.com/uk/about/"/>
<xhtml:link rel="alternate" hreflang="x-default" href="https://example.com/about/"/>
</url>
</urlset>
x-default Usage
x-default indicates page for users whose language/region don't match any hreflang. Use for:
- Language selection page
- English as default for unknown markets
- Can omit if no neutral version exists
URL Structures for Multilingual Sites
| Structure | Example | SEO |
|---|---|---|
| Subdomain | ru.example.com | Cleaner separation, more complex |
| Subfolder | example.com/ru/ | Simpler, shares domain authority |
| ccTLD | example.ru | Strong geo-signal, expensive |
Subfolder (/ru/, /en/) — recommended for most projects.
Timeline
Hreflang audit on existing site (up to 5 languages, 500 pages) — 2–3 days: crawling, error analysis, report. Fixing + auto-generation setup in template/sitemap — 3–5 days. Recheck after 4 weeks — included.







