Running a multilingual website? Then you need to tell Google which language version to show to which users. That's where hreflang comes in.
The problem: Without hreflang, Google might:
- Show Spanish content to English speakers
- Show UK English to US visitors
- Index multiple language versions as duplicate content
- Confuse users with the wrong language variant
The solution: Implement hreflang annotations in your XML sitemap to tell Google exactly which language and region each page targets.
This guide will show you how to add hreflang to your sitemaps, avoid common mistakes, and ensure international visitors see content in their language.
What is hreflang?
hreflang is an HTML attribute (or XML element) that tells search engines:
- What language a page is written in
- What geographic region it targets
- Where to find alternate language versions
Format: hreflang="language-region"
Examples:
en-us= English for United Statesen-gb= English for United Kingdomes-mx= Spanish for Mexicoes-es= Spanish for Spainfr= French (any region)zh-cn= Chinese (Simplified) for Chinazh-tw= Chinese (Traditional) for Taiwan
Why Use hreflang in Sitemaps?
You can implement hreflang in three ways:
- HTML
<link>tags in page headers - HTTP headers for non-HTML files
- XML sitemaps (recommended for large sites - see fundamentals)
Advantages of sitemap implementation:
- Easier to manage for large sites
- Centralized in one place
- No need to edit every page
- Works for all content types
- Easier to audit and debug
When to use sitemaps:
- You have 100+ multilingual pages
- You frequently add new languages
- You want centralized management
- You have a complex language structure
When to use HTML tags:
- Small site (under 100 pages)
- Simple language structure
- You want page-level control
hreflang Syntax in XML Sitemaps
Basic Structure
<?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/page</loc>
<xhtml:link rel="alternate" hreflang="en" href="https://example.com/page" />
<xhtml:link rel="alternate" hreflang="es" href="https://example.com/es/page" />
<xhtml:link rel="alternate" hreflang="fr" href="https://example.com/fr/page" />
</url>
</urlset>
Key components:
xmlns:xhtmlnamespace declaration<xhtml:link>for each language versionrel="alternate"attributehreflangattribute with language codehrefwith the URL for that language
Critical Rule: Reciprocal Links
Every language version must link to ALL other versions, including itself.
Wrong (missing self-reference):
<url>
<loc>https://example.com/page</loc>
<!-- Missing self-reference! -->
<xhtml:link rel="alternate" hreflang="es" href="https://example.com/es/page" />
<xhtml:link rel="alternate" hreflang="fr" href="https://example.com/fr/page" />
</url>
Right (includes self-reference):
<url>
<loc>https://example.com/page</loc>
<xhtml:link rel="alternate" hreflang="en" href="https://example.com/page" />
<xhtml:link rel="alternate" hreflang="es" href="https://example.com/es/page" />
<xhtml:link rel="alternate" hreflang="fr" href="https://example.com/fr/page" />
</url>
Complete Example: Three Languages
<?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">
<!-- English version -->
<url>
<loc>https://example.com/about</loc>
<xhtml:link rel="alternate" hreflang="en" href="https://example.com/about" />
<xhtml:link rel="alternate" hreflang="es" href="https://example.com/es/acerca-de" />
<xhtml:link rel="alternate" hreflang="fr" href="https://example.com/fr/a-propos" />
</url>
<!-- Spanish version -->
<url>
<loc>https://example.com/es/acerca-de</loc>
<xhtml:link rel="alternate" hreflang="en" href="https://example.com/about" />
<xhtml:link rel="alternate" hreflang="es" href="https://example.com/es/acerca-de" />
<xhtml:link rel="alternate" hreflang="fr" href="https://example.com/fr/a-propos" />
</url>
<!-- French version -->
<url>
<loc>https://example.com/fr/a-propos</loc>
<xhtml:link rel="alternate" hreflang="en" href="https://example.com/about" />
<xhtml:link rel="alternate" hreflang="es" href="https://example.com/es/acerca-de" />
<xhtml:link rel="alternate" hreflang="fr" href="https://example.com/fr/a-propos" />
</url>
</urlset>
Notice: Each URL entry has identical <xhtml:link> elements. This is required!
Language and Region Codes
Language-Only Codes
Use when targeting a language regardless of region:
| Code | Language |
|---|---|
en |
English (any region) |
es |
Spanish (any region) |
fr |
French (any region) |
de |
German (any region) |
it |
Italian (any region) |
pt |
Portuguese (any region) |
ja |
Japanese |
ko |
Korean |
zh |
Chinese |
ar |
Arabic |
Language-Region Codes
Use when targeting specific regions:
| Code | Language & Region |
|---|---|
en-us |
English - United States |
en-gb |
English - United Kingdom |
en-ca |
English - Canada |
en-au |
English - Australia |
es-es |
Spanish - Spain |
es-mx |
Spanish - Mexico |
es-ar |
Spanish - Argentina |
pt-br |
Portuguese - Brazil |
pt-pt |
Portuguese - Portugal |
fr-fr |
French - France |
fr-ca |
French - Canada |
de-de |
German - Germany |
de-at |
German - Austria |
de-ch |
German - Switzerland |
zh-cn |
Chinese (Simplified) - China |
zh-tw |
Chinese (Traditional) - Taiwan |
Format: Always lowercase, language first, then region (ISO 639-1 + ISO 3166-1).
The x-default Tag
Use x-default to specify a fallback page for users whose language isn't available:
<url>
<loc>https://example.com/</loc>
<xhtml:link rel="alternate" hreflang="x-default" href="https://example.com/" />
<xhtml:link rel="alternate" hreflang="en" href="https://example.com/" />
<xhtml:link rel="alternate" hreflang="es" href="https://example.com/es/" />
<xhtml:link rel="alternate" hreflang="fr" href="https://example.com/fr/" />
</url>
When to use x-default:
- You have a language selector page
- You want to show English to unmatched users
- You have a global homepage
Common pattern: Set x-default to your primary language (usually English).
How to Generate Multilingual Sitemaps
(For general sitemap creation, see our complete guide)
Method 1: Manual Creation (Small Sites)
For sites with few pages and languages:
<?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">
<!-- Homepage -->
<url>
<loc>https://example.com/</loc>
<xhtml:link rel="alternate" hreflang="x-default" href="https://example.com/" />
<xhtml:link rel="alternate" hreflang="en" href="https://example.com/" />
<xhtml:link rel="alternate" hreflang="es" href="https://example.com/es/" />
</url>
<url>
<loc>https://example.com/es/</loc>
<xhtml:link rel="alternate" hreflang="x-default" href="https://example.com/" />
<xhtml:link rel="alternate" hreflang="en" href="https://example.com/" />
<xhtml:link rel="alternate" hreflang="es" href="https://example.com/es/" />
</url>
<!-- Repeat for each page -->
</urlset>
Method 2: WordPress Plugins
WPML (WordPress Multilingual):
- Automatically generates hreflang sitemaps
- Integrates with Yoast SEO
- Handles all language relationships
Polylang:
- Works with Yoast SEO for sitemap generation
- Automatically adds hreflang annotations
- Supports unlimited languages
TranslatePress:
- Generates hreflang automatically
- Works with existing sitemap plugins
- No manual configuration needed
Method 3: Python Script
For custom sites:
def generate_multilingual_sitemap(pages, languages):
"""
Generate sitemap with hreflang annotations
pages: dict mapping page slugs to language URLs
languages: list of language codes
"""
xml = '<?xml version="1.0" encoding="UTF-8"?>\n'
xml += '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"\n'
xml += ' xmlns:xhtml="http://www.w3.org/1999/xhtml">\n'
for page_slug, urls in pages.items():
# Create entry for each language version
for lang in languages:
if lang in urls:
xml += ' <url>\n'
xml += f' <loc>{urls[lang]}</loc>\n'
# Add x-default (use first language as default)
default_lang = languages[0]
xml += f' <xhtml:link rel="alternate" hreflang="x-default" href="{urls[default_lang]}" />\n'
# Add all language versions
for alt_lang in languages:
if alt_lang in urls:
xml += f' <xhtml:link rel="alternate" hreflang="{alt_lang}" href="{urls[alt_lang]}" />\n'
xml += ' </url>\n'
xml += '</urlset>'
return xml
# Usage
pages = {
'homepage': {
'en': 'https://example.com/',
'es': 'https://example.com/es/',
'fr': 'https://example.com/fr/',
},
'about': {
'en': 'https://example.com/about',
'es': 'https://example.com/es/acerca-de',
'fr': 'https://example.com/fr/a-propos',
},
'contact': {
'en': 'https://example.com/contact',
'es': 'https://example.com/es/contacto',
'fr': 'https://example.com/fr/contact',
},
}
languages = ['en', 'es', 'fr']
sitemap = generate_multilingual_sitemap(pages, languages)
print(sitemap)
For database-driven sites:
import sqlite3
def generate_sitemap_from_db():
"""Generate multilingual sitemap from database"""
conn = sqlite3.connect('website.db')
cursor = conn.cursor()
xml = '<?xml version="1.0" encoding="UTF-8"?>\n'
xml += '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"\n'
xml += ' xmlns:xhtml="http://www.w3.org/1999/xhtml">\n'
# Get all pages with their translations
cursor.execute('''
SELECT p.slug, p.lang, p.url
FROM pages p
WHERE p.published = 1
ORDER BY p.slug, p.lang
''')
# Group by slug
pages = {}
for slug, lang, url in cursor.fetchall():
if slug not in pages:
pages[slug] = {}
pages[slug][lang] = url
# Generate sitemap
for slug, translations in pages.items():
for lang, url in translations.items():
xml += ' <url>\n'
xml += f' <loc>{url}</loc>\n'
# Add x-default (English)
if 'en' in translations:
xml += f' <xhtml:link rel="alternate" hreflang="x-default" href="{translations["en"]}" />\n'
# Add all translations
for alt_lang, alt_url in translations.items():
xml += f' <xhtml:link rel="alternate" hreflang="{alt_lang}" href="{alt_url}" />\n'
xml += ' </url>\n'
xml += '</urlset>'
conn.close()
return xml
Common hreflang Mistakes
Mistake #1: Missing Self-Reference
Wrong:
<url>
<loc>https://example.com/page</loc>
<!-- Missing hreflang="en" for itself! -->
<xhtml:link rel="alternate" hreflang="es" href="https://example.com/es/page" />
</url>
Right:
<url>
<loc>https://example.com/page</loc>
<xhtml:link rel="alternate" hreflang="en" href="https://example.com/page" />
<xhtml:link rel="alternate" hreflang="es" href="https://example.com/es/page" />
</url>
Mistake #2: Non-Reciprocal Links
Wrong (English page links to Spanish, but Spanish doesn't link back):
<!-- English page -->
<url>
<loc>https://example.com/page</loc>
<xhtml:link rel="alternate" hreflang="en" href="https://example.com/page" />
<xhtml:link rel="alternate" hreflang="es" href="https://example.com/es/page" />
</url>
<!-- Spanish page - MISSING English link! -->
<url>
<loc>https://example.com/es/page</loc>
<xhtml:link rel="alternate" hreflang="es" href="https://example.com/es/page" />
</url>
Right (both pages have identical hreflang annotations):
<!-- English page -->
<url>
<loc>https://example.com/page</loc>
<xhtml:link rel="alternate" hreflang="en" href="https://example.com/page" />
<xhtml:link rel="alternate" hreflang="es" href="https://example.com/es/page" />
</url>
<!-- Spanish page -->
<url>
<loc>https://example.com/es/page</loc>
<xhtml:link rel="alternate" hreflang="en" href="https://example.com/page" />
<xhtml:link rel="alternate" hreflang="es" href="https://example.com/es/page" />
</url>
Mistake #3: Wrong Language Codes
Wrong:
<xhtml:link rel="alternate" hreflang="EN-US" href="..." /> ← Uppercase
<xhtml:link rel="alternate" hreflang="en_us" href="..." /> ← Underscore
<xhtml:link rel="alternate" hreflang="english" href="..." /> ← Full word
Right:
<xhtml:link rel="alternate" hreflang="en-us" href="..." /> ← Lowercase, hyphen
Mistake #4: Pointing to Different Content
hreflang should link to equivalent content in different languages, not different pages.
Wrong:
<!-- English: Product page -->
<url>
<loc>https://example.com/products/shoes</loc>
<xhtml:link rel="alternate" hreflang="es" href="https://example.com/es/blog" /> ← Different content!
</url>
Right:
<!-- English: Product page -->
<url>
<loc>https://example.com/products/shoes</loc>
<xhtml:link rel="alternate" hreflang="es" href="https://example.com/es/productos/zapatos" /> ← Same content, different language
</url>
Mistake #5: Missing Namespace
Wrong:
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<!-- Missing xmlns:xhtml! -->
<xhtml:link rel="alternate" hreflang="es" href="..." />
</urlset>
Right:
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:xhtml="http://www.w3.org/1999/xhtml">
<xhtml:link rel="alternate" hreflang="es" href="..." />
</urlset>
Testing and Validation
Google Search Console
- Submit your sitemap
- Wait 24-48 hours
- Go to International Targeting → Language
- Check for hreflang errors
Common errors:
- "No return tags" (missing reciprocal links)
- "Incorrect hreflang value" (wrong language code)
- "Missing self-reference"
hreflang Testing Tools
Recommended tools:
- hreflang Tags Testing Tool by Aleyda Solis
- Merkle hreflang Tag Testing Tool
- Sitebulb (paid, but comprehensive)
Manual Testing
Check your sitemap:
curl https://example.com/sitemap.xml | grep hreflang
Verify reciprocal links:
- Find a URL in your sitemap
- Check all its hreflang links
- Visit each linked URL
- Verify it has the same hreflang annotations
Best Practices
1. Keep It Simple
Start with language-only codes if you don't need regional targeting:
<xhtml:link rel="alternate" hreflang="en" href="..." />
<xhtml:link rel="alternate" hreflang="es" href="..." />
Add regions only when necessary:
<xhtml:link rel="alternate" hreflang="en-us" href="..." />
<xhtml:link rel="alternate" hreflang="en-gb" href="..." />
2. Use Absolute URLs
Always:
<xhtml:link rel="alternate" hreflang="es" href="https://example.com/es/page" />
Never:
<xhtml:link rel="alternate" hreflang="es" href="/es/page" />
3. Include x-default
Specify a default for unmatched languages:
<xhtml:link rel="alternate" hreflang="x-default" href="https://example.com/" />
4. Automate Generation
Don't manually maintain hreflang annotations. Use:
- CMS plugins (WordPress, Drupal)
- Build scripts (for static sites)
- Database queries (for custom sites)
5. Monitor Regularly
Monthly checklist:
- [ ] Check Search Console for hreflang errors
- [ ] Verify new pages have hreflang annotations
- [ ] Test a sample of URLs for reciprocal links
- [ ] Audit for broken language URLs
Next Steps
Now that you understand hreflang in sitemaps:
- Audit your current setup - Do you have hreflang implemented?
- Plan your language structure - Which languages and regions do you target?
- Generate your sitemap - Add hreflang annotations
- Test thoroughly - Use validation tools
- Submit to Search Console - Monitor for errors
- Learn about organization - Read our sitemap index guide for managing multiple languages
Key Takeaways
- hreflang tells Google which language version to show users
- Implement in sitemaps for easier management on large sites
- Every language version must link to ALL others, including itself
- Use lowercase language codes (en-us, not EN-US)
- Include x-default for a fallback language
- Test with Google Search Console and validation tools
Bottom line: Proper hreflang implementation prevents duplicate content issues and ensures international visitors see content in their language.
Ready to audit your multilingual sitemap? Visualize your sitemap structure to see all your hreflang relationships at a glance.