International SEO: Master Hreflang Tags for Multi-Language Sites
Master hreflang implementation for international websites. Learn to target multiple languages and regions correctly, avoid common mistakes.
Introduction
Expanding your website to multiple languages or regions? Hreflang tags are essential for international SEO success. They tell search engines which language and regional variations of your content exist, ensuring users see the right version in search results.
Without proper hreflang implementation, you risk:
- Users seeing the wrong language version
- Duplicate content issues across regional sites
- Wasted crawl budget on wrong-region pages
- Lost traffic and conversions
In this comprehensive guide, we'll master hreflang implementation from basics to advanced strategies.
What is Hreflang?
Hreflang is an HTML attribute that specifies the language and optional geographical targeting of a web page. It helps search engines:
- Serve correct language to users based on their location/language settings
- Avoid duplicate content penalties for similar content in different languages
- Consolidate signals from equivalent pages across regions
- Improve user experience by showing relevant versions
Hreflang was introduced by Google and is also supported by Yandex. Bing uses a different system (language meta tags) but understanding hreflang is still valuable.
When You Need Hreflang
Use Cases
Scenario | Example | Need Hreflang? |
---|---|---|
Same language, different regions | US vs UK English | ✅ Yes |
Different languages | English vs Spanish | ✅ Yes |
Regional targeting | Canada (EN/FR) | ✅ Yes |
Translated content | Blog in 5 languages | ✅ Yes |
Single language site | English only | ❌ No |
Completely different content | Different products per region | ❌ No |
Hreflang is for the SAME content in different languages/regions. If content is substantially different, don't use hreflang—these are separate pages.
Hreflang Syntax
The hreflang attribute follows ISO standards:
<!-- Basic syntax -->
<link rel="alternate" hreflang="language-region" href="url" />
<!-- Language only (ISO 639-1) -->
<link rel="alternate" hreflang="en" href="https://example.com/en/" />
<!-- Language + Region (ISO 639-1 + ISO 3166-1 Alpha 2) -->
<link rel="alternate" hreflang="en-us" href="https://example.com/en-us/" />
<!-- Always include self-referencing tag -->
<link rel="alternate" hreflang="en-gb" href="https://example.com/en-gb/" />
Language Codes (ISO 639-1)
en = English
es = Spanish
fr = French
de = German
it = Italian
pt = Portuguese
ja = Japanese
zh = Chinese
ar = Arabic
ru = Russian
ko = Korean
nl = Dutch
sv = Swedish
pl = Polish
Region Codes (ISO 3166-1 Alpha 2)
US = United States
GB = United Kingdom
CA = Canada
AU = Australia
DE = Germany
FR = France
ES = Spain
IT = Italy
BR = Brazil
MX = Mexico
JP = Japan
CN = China
IN = India
Special Value: x-default
The x-default
tag specifies the default page when no language/region matches:
<!-- Fallback for unmatched languages/regions -->
<link rel="alternate" hreflang="x-default" href="https://example.com/" />
Always include an x-default tag pointing to your language selector page or most international version.
Implementation Methods
1. HTML Link Tags (Recommended)
Add hreflang tags to the <head>
section:
<!DOCTYPE html>
<html lang="en-US">
<head>
<meta charset="UTF-8">
<title>Example Page</title>
<!-- Self-referencing -->
<link rel="alternate" hreflang="en-us" href="https://example.com/en-us/page" />
<!-- Other language versions -->
<link rel="alternate" hreflang="en-gb" href="https://example.com/en-gb/page" />
<link rel="alternate" hreflang="es-es" href="https://example.com/es-es/page" />
<link rel="alternate" hreflang="es-mx" href="https://example.com/es-mx/page" />
<link rel="alternate" hreflang="fr-fr" href="https://example.com/fr-fr/page" />
<link rel="alternate" hreflang="de-de" href="https://example.com/de-de/page" />
<!-- Default fallback -->
<link rel="alternate" hreflang="x-default" href="https://example.com/en/" />
</head>
<body>
<!-- Content -->
</body>
</html>
Advantages:
- ✅ Easy to implement dynamically
- ✅ Page-specific control
- ✅ Works with JavaScript frameworks
Disadvantages:
- ❌ Adds to HTML size
- ❌ Must be on every page
- ❌ Can be numerous for many languages
2. HTTP Headers
Use HTTP headers for non-HTML files (PDFs, images):
# HTTP Response Header
Link: <https://example.com/en-us/document.pdf>; rel="alternate"; hreflang="en-us",
<https://example.com/es-es/document.pdf>; rel="alternate"; hreflang="es-es",
<https://example.com/fr-fr/document.pdf>; rel="alternate"; hreflang="fr-fr"
Server configuration:
# Apache .htaccess
<FilesMatch "document.pdf$">
Header set Link "<https://example.com/en-us/document.pdf>; rel="alternate"; hreflang="en-us""
</FilesMatch>
# Nginx
location ~* .pdf$ {
add_header Link '<https://example.com/en-us/document.pdf>; rel="alternate"; hreflang="en-us"';
}
3. XML Sitemap
Include hreflang in your 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">
<!-- US English page -->
<url>
<loc>https://example.com/en-us/page</loc>
<xhtml:link
rel="alternate"
hreflang="en-us"
href="https://example.com/en-us/page" />
<xhtml:link
rel="alternate"
hreflang="en-gb"
href="https://example.com/en-gb/page" />
<xhtml:link
rel="alternate"
hreflang="es-es"
href="https://example.com/es-es/page" />
<xhtml:link
rel="alternate"
hreflang="x-default"
href="https://example.com/en/" />
</url>
<!-- UK English page -->
<url>
<loc>https://example.com/en-gb/page</loc>
<xhtml:link
rel="alternate"
hreflang="en-us"
href="https://example.com/en-us/page" />
<xhtml:link
rel="alternate"
hreflang="en-gb"
href="https://example.com/en-gb/page" />
<xhtml:link
rel="alternate"
hreflang="es-es"
href="https://example.com/es-es/page" />
<xhtml:link
rel="alternate"
hreflang="x-default"
href="https://example.com/en/" />
</url>
<!-- Spanish page -->
<url>
<loc>https://example.com/es-es/page</loc>
<xhtml:link
rel="alternate"
hreflang="en-us"
href="https://example.com/en-us/page" />
<xhtml:link
rel="alternate"
hreflang="en-gb"
href="https://example.com/en-gb/page" />
<xhtml:link
rel="alternate"
hreflang="es-es"
href="https://example.com/es-es/page" />
<xhtml:link
rel="alternate"
hreflang="x-default"
href="https://example.com/en/" />
</url>
</urlset>
Advantages:
- ✅ Centralized management
- ✅ Reduces HTML bloat
- ✅ Good for large sites
Disadvantages:
- ❌ Harder to maintain
- ❌ All versions must be in sitemap
- ❌ Sitemap file can get large
Most sites use HTML link tags for flexibility. Choose based on your site size and maintenance capabilities.
Common Patterns
Pattern 1: Multiple Languages, Single Region
<!-- Global site with multiple languages -->
<link rel="alternate" hreflang="en" href="https://example.com/en/" />
<link rel="alternate" hreflang="es" href="https://example.com/es/" />
<link rel="alternate" hreflang="fr" href="https://example.com/fr/" />
<link rel="alternate" hreflang="de" href="https://example.com/de/" />
<link rel="alternate" hreflang="x-default" href="https://example.com/en/" />
Pattern 2: Same Language, Multiple Regions
<!-- English content for different regions -->
<link rel="alternate" hreflang="en-us" href="https://example.com/us/" />
<link rel="alternate" hreflang="en-gb" href="https://example.com/uk/" />
<link rel="alternate" hreflang="en-au" href="https://example.com/au/" />
<link rel="alternate" hreflang="en-ca" href="https://example.com/ca/" />
<link rel="alternate" hreflang="x-default" href="https://example.com/us/" />
Pattern 3: Multiple Languages AND Regions
<!-- Complex: Multiple languages across multiple regions -->
<!-- US -->
<link rel="alternate" hreflang="en-us" href="https://example.com/en-us/" />
<link rel="alternate" hreflang="es-us" href="https://example.com/es-us/" />
<!-- Mexico -->
<link rel="alternate" hreflang="es-mx" href="https://example.com/es-mx/" />
<link rel="alternate" hreflang="en-mx" href="https://example.com/en-mx/" />
<!-- Spain -->
<link rel="alternate" hreflang="es-es" href="https://example.com/es-es/" />
<link rel="alternate" hreflang="en-es" href="https://example.com/en-es/" />
<!-- UK -->
<link rel="alternate" hreflang="en-gb" href="https://example.com/en-gb/" />
<!-- Generic fallbacks -->
<link rel="alternate" hreflang="en" href="https://example.com/en/" />
<link rel="alternate" hreflang="es" href="https://example.com/es/" />
<!-- Default -->
<link rel="alternate" hreflang="x-default" href="https://example.com/" />
Pattern 4: Subdomain Structure
<!-- Different languages on subdomains -->
<link rel="alternate" hreflang="en" href="https://en.example.com/page/" />
<link rel="alternate" hreflang="es" href="https://es.example.com/page/" />
<link rel="alternate" hreflang="fr" href="https://fr.example.com/page/" />
<link rel="alternate" hreflang="x-default" href="https://www.example.com/" />
Pattern 5: ccTLD Structure
<!-- Country-specific domains -->
<link rel="alternate" hreflang="en-us" href="https://example.com/page/" />
<link rel="alternate" hreflang="en-gb" href="https://example.co.uk/page/" />
<link rel="alternate" hreflang="de-de" href="https://example.de/page/" />
<link rel="alternate" hreflang="fr-fr" href="https://example.fr/page/" />
<link rel="alternate" hreflang="x-default" href="https://example.com/page/" />
Dynamic Implementation
Next.js Example
// components/HreflangTags.tsx
import Head from 'next/head';
import { useRouter } from 'next/router';
interface HreflangTagsProps {
alternates: Record<string, string>;
defaultUrl: string;
}
export function HreflangTags({ alternates, defaultUrl }: HreflangTagsProps) {
const router = useRouter();
const currentLocale = router.locale || 'en';
return (
<Head>
{/* Self-referencing */}
<link
rel="alternate"
hreflang={currentLocale}
href={alternates[currentLocale]}
/>
{/* All alternates */}
{Object.entries(alternates).map(([locale, url]) => (
<link
key={locale}
rel="alternate"
hreflang={locale}
href={url}
/>
))}
{/* Default */}
<link
rel="alternate"
hreflang="x-default"
href={defaultUrl}
/>
</Head>
);
}
// Usage in page
export default function ProductPage({ product }) {
const alternates = {
'en-us': `https://example.com/en-us/product/${product.slug}`,
'en-gb': `https://example.com/en-gb/product/${product.slug}`,
'es-es': `https://example.com/es-es/producto/${product.slug}`,
'fr-fr': `https://example.com/fr-fr/produit/${product.slug}`,
};
return (
<>
<HreflangTags
alternates={alternates}
defaultUrl={alternates['en-us']}
/>
<main>{/* Product content */}</main>
</>
);
}
React/Node.js API
// Generate hreflang tags dynamically
interface HreflangConfig {
locales: string[];
baseUrl: string;
urlPattern: (locale: string, slug: string) => string;
}
function generateHreflangTags(
config: HreflangConfig,
slug: string,
defaultLocale: string = 'en-us'
): string {
const tags: string[] = [];
// Generate tag for each locale
config.locales.forEach(locale => {
const url = config.urlPattern(locale, slug);
tags.push(`<link rel="alternate" hreflang="${locale}" href="${url}" />`);
});
// Add x-default
const defaultUrl = config.urlPattern(defaultLocale, slug);
tags.push(`<link rel="alternate" hreflang="x-default" href="${defaultUrl}" />`);
return tags.join('\n');
}
// Usage
const config: HreflangConfig = {
locales: ['en-us', 'en-gb', 'es-es', 'fr-fr', 'de-de'],
baseUrl: 'https://example.com',
urlPattern: (locale, slug) => `https://example.com/${locale}/${slug}`
};
const hreflangTags = generateHreflangTags(config, 'my-article', 'en-us');
Critical Rules
Rule 1: Bidirectional Linking
Every page must link to ALL other language versions AND itself:
<!-- ❌ WRONG: Missing reciprocal links -->
<!-- On EN page -->
<link rel="alternate" hreflang="es" href=".../es/" />
<!-- But ES page doesn't link back to EN -->
<!-- ✅ CORRECT: Bidirectional -->
<!-- On EN page -->
<link rel="alternate" hreflang="en" href=".../en/" />
<link rel="alternate" hreflang="es" href=".../es/" />
<!-- On ES page -->
<link rel="alternate" hreflang="en" href=".../en/" />
<link rel="alternate" hreflang="es" href=".../es/" />
If Page A links to Page B, but Page B doesn't link back to Page A, the hreflang will be ignored by Google.
Rule 2: Self-Referencing Required
Every page must include a self-referencing hreflang tag:
<!-- On https://example.com/en-us/page -->
<link rel="alternate" hreflang="en-us" href="https://example.com/en-us/page" />
Rule 3: Use Absolute URLs
<!-- ❌ WRONG: Relative URLs -->
<link rel="alternate" hreflang="es" href="/es/page" />
<!-- ✅ CORRECT: Absolute URLs with protocol -->
<link rel="alternate" hreflang="es" href="https://example.com/es/page" />
Rule 4: Consistent Across All Pages
If 10 pages are part of a language cluster, ALL 10 must have hreflang pointing to each other:
// Example: 3 languages × 5 pages = 15 total pages
// Each page needs: 3 hreflang tags (one per language)
Page A: EN, ES, FR versions → each links to all 3
Page B: EN, ES, FR versions → each links to all 3
Page C: EN, ES, FR versions → each links to all 3
Page D: EN, ES, FR versions → each links to all 3
Page E: EN, ES, FR versions → each links to all 3
Common Mistakes to Avoid
Mistake 1: Language Without Region When Region Matters
<!-- ❌ Too generic when differences exist -->
<link rel="alternate" hreflang="en" href=".../en/" />
<link rel="alternate" hreflang="es" href=".../es/" />
<!-- Spanish for US vs Spain are different (currency, dialect) -->
<!-- ✅ Be specific when needed -->
<link rel="alternate" hreflang="en-us" href=".../en-us/" />
<link rel="alternate" hreflang="es-us" href=".../es-us/" />
<link rel="alternate" hreflang="es-es" href=".../es-es/" />
Mistake 2: Conflicting with Canonical Tags
<!-- ❌ WRONG: Canonical contradicts hreflang -->
<!-- On ES page -->
<link rel="canonical" href="https://example.com/en/" />
<link rel="alternate" hreflang="es" href="https://example.com/es/" />
<!-- Don't canonical to a different language! -->
<!-- ✅ CORRECT: Each language has self-canonical -->
<!-- On ES page -->
<link rel="canonical" href="https://example.com/es/" />
<link rel="alternate" hreflang="es" href="https://example.com/es/" />
<link rel="alternate" hreflang="en" href="https://example.com/en/" />
Mistake 3: Missing x-default
<!-- ❌ No fallback for unmatched languages -->
<link rel="alternate" hreflang="en-us" href=".../en-us/" />
<link rel="alternate" hreflang="es-es" href=".../es-es/" />
<!-- What happens for Japanese user? -->
<!-- ✅ Always include x-default -->
<link rel="alternate" hreflang="en-us" href=".../en-us/" />
<link rel="alternate" hreflang="es-es" href=".../es-es/" />
<link rel="alternate" hreflang="x-default" href=".../en-us/" />
Mistake 4: Incorrect Language/Region Codes
<!-- ❌ WRONG codes -->
<link rel="alternate" hreflang="en_US" href="..." /> <!-- underscore -->
<link rel="alternate" hreflang="EN-us" href="..." /> <!-- uppercase language -->
<link rel="alternate" hreflang="en-usa" href="..." /> <!-- 3-letter country -->
<!-- ✅ CORRECT format -->
<link rel="alternate" hreflang="en-US" href="..." />
<!-- language (lowercase) - region (UPPERCASE) -->
Mistake 5: Non-Equivalent Content
<!-- ❌ Different products in different regions -->
<!-- US: Selling product A -->
<!-- UK: Selling product B -->
<!-- Don't use hreflang - these are different pages! -->
<!-- ✅ Only use for equivalent content -->
<!-- US: iPhone 15 Pro page -->
<!-- UK: iPhone 15 Pro page (same product, localized) -->
<!-- Use hreflang - same content, different region -->
Testing and Validation
Google Search Console
- Navigate to Settings → International Targeting
- Check Language tab for hreflang errors
- Review error types:
- Missing return links
- Incorrect language codes
- No matching URLs
Hreflang Testing Tools
# Command line validation
curl -s https://example.com/page | grep -i hreflang
# Check all hreflang tags
curl -s https://example.com/page | grep -oP 'hreflang="[^"]*".*?href="[^"]*"'
# Test bidirectional linking
function test_hreflang() {
url1=$1
url2=$2
# Check if url1 links to url2
links_to=$(curl -s $url1 | grep -c $url2)
# Check if url2 links to url1
links_back=$(curl -s $url2 | grep -c $url1)
if [ $links_to -gt 0 ] && [ $links_back -gt 0 ]; then
echo "✓ Bidirectional linking OK"
else
echo "✗ Missing reciprocal links"
fi
}
test_hreflang "https://example.com/en/" "https://example.com/es/"
Automated Validation Script
// Validate hreflang implementation
import { JSDOM } from 'jsdom';
interface HreflangTag {
hreflang: string;
href: string;
}
async function validateHreflang(url: string): Promise<{valid: boolean, errors: string[]}> {
const errors: string[] = [];
// Fetch page
const html = await fetch(url).then(r => r.text());
const dom = new JSDOM(html);
const doc = dom.window.document;
// Extract hreflang tags
const tags = Array.from(doc.querySelectorAll('link[rel="alternate"][hreflang]'))
.map(link => ({
hreflang: link.getAttribute('hreflang')!,
href: link.getAttribute('href')!
}));
if (tags.length === 0) {
errors.push('No hreflang tags found');
return { valid: false, errors };
}
// Check for self-referencing
const hasSelfRef = tags.some(tag => tag.href === url);
if (!hasSelfRef) {
errors.push('Missing self-referencing hreflang tag');
}
// Check for x-default
const hasDefault = tags.some(tag => tag.hreflang === 'x-default');
if (!hasDefault) {
errors.push('Missing x-default hreflang tag');
}
// Check absolute URLs
tags.forEach(tag => {
if (!tag.href.startsWith('http')) {
errors.push(`Relative URL found: ${tag.href}`);
}
});
// Validate bidirectional linking
for (const tag of tags) {
if (tag.href === url) continue; // Skip self
try {
const targetHtml = await fetch(tag.href).then(r => r.text());
const targetDom = new JSDOM(targetHtml);
const targetLinks = targetDom.window.document.querySelectorAll('link[rel="alternate"][hreflang]');
const linksBack = Array.from(targetLinks).some(link =>
link.getAttribute('href') === url
);
if (!linksBack) {
errors.push(`${tag.href} doesn't link back to ${url}`);
}
} catch (e) {
errors.push(`Failed to fetch ${tag.href}: ${e.message}`);
}
}
return {
valid: errors.length === 0,
errors
};
}
// Usage
const result = await validateHreflang('https://example.com/en-us/page');
console.log('Valid:', result.valid);
console.log('Errors:', result.errors);
Language Selector UI
Provide users with a way to switch languages:
<!-- Language/Region selector -->
<div class="language-selector">
<button class="current-language">
🌐 English (US)
<svg><!-- dropdown icon --></svg>
</button>
<ul class="language-menu">
<li><a href="/en-us/" hreflang="en-us">English (US)</a></li>
<li><a href="/en-gb/" hreflang="en-gb">English (UK)</a></li>
<li><a href="/es-es/" hreflang="es-es">Español (España)</a></li>
<li><a href="/es-mx/" hreflang="es-mx">Español (México)</a></li>
<li><a href="/fr-fr/" hreflang="fr-fr">Français</a></li>
<li><a href="/de-de/" hreflang="de-de">Deutsch</a></li>
</ul>
</div>
<script>
// Auto-detect and suggest language
const userLang = navigator.language || navigator.userLanguage;
const currentLang = document.documentElement.lang;
if (userLang !== currentLang) {
const suggested = `/${userLang}/${window.location.pathname}`;
showLanguageSuggestion(suggested);
}
</script>
Monitoring and Maintenance
Regular Audits
// Monthly hreflang audit checklist
const auditChecklist = [
'Verify all pages have hreflang tags',
'Check for broken alternate URLs',
'Validate bidirectional linking',
'Confirm x-default is present',
'Test new pages have proper tags',
'Review Google Search Console errors',
'Check language selector works',
'Verify canonical tags align',
'Test with VPN from different regions',
'Validate language codes are correct'
];
Conclusion
Hreflang implementation is essential for international SEO success. Proper configuration ensures:
- Users see correct language/region versions
- Duplicate content is managed properly
- Search engines understand language alternatives
- International traffic is maximized
Key Takeaways:
- ✅ Use correct ISO language and region codes
- ✅ Implement bidirectional linking (reciprocal)
- ✅ Include self-referencing tags on every page
- ✅ Always add x-default fallback
- ✅ Use absolute URLs with protocol
- ✅ Keep consistent across all versions
- ✅ Don't conflict with canonical tags
- ✅ Only use for equivalent content
- ✅ Test thoroughly before launch
- ✅ Monitor in Google Search Console
International SEO is complex, but mastering hreflang is a crucial step toward global success.
Next Steps
- Learn about URL structure for international sites
- Explore content localization best practices
- Study regional hosting considerations
- Master international technical SEO
Related Resources: