Generating & Configuring Hreflang
Overview
Hreflang tells search engines which language and regional version of a page to serve based on the searcher's locale. In multilingual setups, misconfigured hreflang tags cause duplicate content cannibalization, wrong-region SERP rankings, and lost international traffic.
Google's hreflang parser is strict and unforgiving: if alternate links are not bidirectional (reciprocal), Google completely ignores them [1]. Furthermore, modern AI search engines (ChatGPT, Perplexity, Claude) inspect locale-specific canonicals and alternates to attribute region-specific citations.
The job: map alternate URLs, enforce reciprocal linkage and x-default, validate language/region codes against ISO standards, and generate clean HTML tags, XML sitemap blocks, or framework metadata.
The Four Non-Negotiable Hreflang Laws
- Bidirectional Reciprocity is Mandatory:
- If Page A (
https://example.com/en) specifies Page B (https://example.com/es) as its Spanish alternate, Page B must specify Page A as its English alternate.
- If reciprocity is broken anywhere in the chain, Google discards the tags for both pages [1].
- Self-Referential Links are Required:
- Every page's alternate set must include an alternate link pointing to itself with its own language tag.
x-default Fallback Must Be Defined:
- Always include an
x-default tag pointing to a generic global landing page, language selector, or default language version for users whose locale is not explicitly targeted.
- Absolute URLs Only:
- Relative URLs (
/es/about) are invalid in hreflang and ignored by crawlers. All URLs must be fully-qualified absolute HTTPS URLs (https://example.com/es/about).
International Architecture Decision Matrix
| Strategy |
URL Structure |
Best For |
Trade-offs |
| Subdirectories (Recommended) |
example.com/es/ |
SaaS, docs, startups, content sites |
Consolidates all domain authority; easy to maintain; low cost. |
| Subdomains |
es.example.com |
Distinct multi-region server infrastructure |
Splits domain authority; requires separate DNS/SSL; cookie hurdles. |
| ccTLDs |
example.es |
Localized e-commerce with in-country legal entities |
Highest local trust signal; expensive to buy and maintain multiple domains. |
Avoid cookie/session-based language switching or IP-auto-redirecting crawlers without offering crawlable alternate URLs.
The Flow
Inventory Locales & URLs:
Create or inspect your locale mapping. Ensure every localized URL has an equivalent translated target.
Example hreflang.json:
[
{
"group": "https://example.com/en",
"alternates": [
{ "lang": "en", "url": "https://example.com/en" },
{ "lang": "es", "url": "https://example.com/es" },
{ "lang": "fr-CA", "url": "https://example.com/fr-ca" },
{ "lang": "x-default", "url": "https://example.com/" }
]
},
{
"group": "https://example.com/es",
"alternates": [
{ "lang": "en", "url": "https://example.com/en" },
{ "lang": "es", "url": "https://example.com/es" },
{ "lang": "fr-CA", "url": "https://example.com/fr-ca" },
{ "lang": "x-default", "url": "https://example.com/" }
]
}
]
Validate with Deterministic Tooling:
Run scripts/hreflang-tool.js to automatically catch syntax errors, non-reciprocal links, and invalid country codes:
node scripts/hreflang-tool.js validate hreflang.json --strict
Generate Tags for Your Stack:
- HTML
<head> tags:node scripts/hreflang-tool.js generate hreflang.json --format html
- XML Sitemap blocks:
node scripts/hreflang-tool.js generate hreflang.json --format xml
- Next.js App Router metadata:
node scripts/hreflang-tool.js generate hreflang.json --format nextjs
Ship via Pull Request:
Open a PR with the updated layout metadata, sitemap configuration, or head components. Verify the branch passes CI with python scripts/ci-validate.py and node --test test/hreflang.test.js.
Code Implementations
Next.js (App Router)
// app/[locale]/layout.tsx
import { Metadata } from 'next';
export async function generateMetadata({ params }): Promise<Metadata> {
const { locale } = await params;
return {
alternates: {
canonical: `https://example.com/${locale}`,
languages: {
'en': 'https://example.com/en',
'es': 'https://example.com/es',
'fr-CA': 'https://example.com/fr-ca',
'x-default': 'https://example.com/',
},
},
};
}
Astro
---
// src/layouts/BaseLayout.astro
const { currentLocale } = Astro;
---
<head>
<link rel="canonical" href={Astro.url.href} />
<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-CA" href="https://example.com/fr-ca" />
<link rel="alternate" hreflang="x-default" href="https://example.com/" />
</head>
XML Sitemap Alternates
(Best when pages have dozens of language variations to prevent HTML <head> bloat)
<url>
<loc>https://example.com/en</loc>
<xhtml:link rel="alternate" hreflang="en" href="https://example.com/en" />
<xhtml:link rel="alternate" hreflang="es" href="https://example.com/es" />
<xhtml:link rel="alternate" hreflang="x-default" href="https://example.com/" />
</url>
Common Mistakes & Anti-Patterns
- Using underscores (
en_US): Invalid. Hreflang strictly mandates hyphens (en-US).
- Targeting language with a country code alone (
hreflang="uk"): uk is the Ukrainian language code, NOT the United Kingdom country code. For UK English, use en-GB.
- Missing reciprocal links: Forgetting to update older localized versions when launching a new language (e.g. adding French without updating English and Spanish to point to French).
- Pointing hreflang to redirected URLs (301/302): Alternate links must target the 200 OK canonical URL directly.
- Translating URLs without translating content: Creating shallow language shells that share 90% identical English copy results in soft 404 or duplicate content penalties.
1---2name: generating-hreflang3description: Use when setting up international and multilingual SEO — configuring hreflang alternate tags, XML sitemap language alternates, x-default fallbacks, and multi-region targeting. Enforces Google's strict bidirectional reciprocity rule, ISO 639-1 language and ISO 3166-1 region validation, and generates framework-native code (Next.js, Astro, SvelteKit) or sitemap entries. Triggers on "hreflang", "multilingual SEO", "multi-language sitemap", "international SEO", "x-default", "localize site for SEO", "alternate language tags", or when expanding a site to multiple locales.4---56# Generating & Configuring Hreflang78## Overview910Hreflang tells search engines which language and regional version of a page to serve based on the searcher's locale. In multilingual setups, misconfigured hreflang tags cause duplicate content cannibalization, wrong-region SERP rankings, and lost international traffic.1112Google's hreflang parser is strict and unforgiving: **if alternate links are not bidirectional (reciprocal), Google completely ignores them** [1]. Furthermore, modern AI search engines (ChatGPT, Perplexity, Claude) inspect locale-specific canonicals and alternates to attribute region-specific citations.1314The job: map alternate URLs, enforce reciprocal linkage and `x-default`, validate language/region codes against ISO standards, and generate clean HTML tags, XML sitemap blocks, or framework metadata.1516---1718## The Four Non-Negotiable Hreflang Laws19201. **Bidirectional Reciprocity is Mandatory:**21 - If Page A (`https://example.com/en`) specifies Page B (`https://example.com/es`) as its Spanish alternate, Page B **must** specify Page A as its English alternate.22 - If reciprocity is broken anywhere in the chain, Google discards the tags for both pages [1].232. **Self-Referential Links are Required:**24 - Every page's alternate set must include an alternate link pointing to itself with its own language tag.253. **`x-default` Fallback Must Be Defined:**26 - Always include an `x-default` tag pointing to a generic global landing page, language selector, or default language version for users whose locale is not explicitly targeted.274. **Absolute URLs Only:**28 - Relative URLs (`/es/about`) are invalid in hreflang and ignored by crawlers. All URLs must be fully-qualified absolute HTTPS URLs (`https://example.com/es/about`).2930---3132## International Architecture Decision Matrix3334| Strategy | URL Structure | Best For | Trade-offs |35|---|---|---|---|36| **Subdirectories** *(Recommended)* | `example.com/es/` | SaaS, docs, startups, content sites | Consolidates all domain authority; easy to maintain; low cost. |37| **Subdomains** | `es.example.com` | Distinct multi-region server infrastructure | Splits domain authority; requires separate DNS/SSL; cookie hurdles. |38| **ccTLDs** | `example.es` | Localized e-commerce with in-country legal entities | Highest local trust signal; expensive to buy and maintain multiple domains. |3940*Avoid cookie/session-based language switching or IP-auto-redirecting crawlers without offering crawlable alternate URLs.*4142---4344## The Flow45461. **Inventory Locales & URLs:**47 Create or inspect your locale mapping. Ensure every localized URL has an equivalent translated target.48 Example `hreflang.json`:49 ```json50 [51 {52 "group": "https://example.com/en",53 "alternates": [54 { "lang": "en", "url": "https://example.com/en" },55 { "lang": "es", "url": "https://example.com/es" },56 { "lang": "fr-CA", "url": "https://example.com/fr-ca" },57 { "lang": "x-default", "url": "https://example.com/" }58 ]59 },60 {61 "group": "https://example.com/es",62 "alternates": [63 { "lang": "en", "url": "https://example.com/en" },64 { "lang": "es", "url": "https://example.com/es" },65 { "lang": "fr-CA", "url": "https://example.com/fr-ca" },66 { "lang": "x-default", "url": "https://example.com/" }67 ]68 }69 ]70 ```71722. **Validate with Deterministic Tooling:**73 Run `scripts/hreflang-tool.js` to automatically catch syntax errors, non-reciprocal links, and invalid country codes:74 ```bash75 node scripts/hreflang-tool.js validate hreflang.json --strict76 ```77783. **Generate Tags for Your Stack:**79 - **HTML `<head>` tags:**80 ```bash81 node scripts/hreflang-tool.js generate hreflang.json --format html82 ```83 - **XML Sitemap blocks:**84 ```bash85 node scripts/hreflang-tool.js generate hreflang.json --format xml86 ```87 - **Next.js App Router metadata:**88 ```bash89 node scripts/hreflang-tool.js generate hreflang.json --format nextjs90 ```91924. **Ship via Pull Request:**93 Open a PR with the updated layout metadata, sitemap configuration, or head components. Verify the branch passes CI with `python scripts/ci-validate.py` and `node --test test/hreflang.test.js`.9495---9697## Code Implementations9899### Next.js (App Router)100```typescript101// app/[locale]/layout.tsx102import { Metadata } from 'next';103104export async function generateMetadata({ params }): Promise<Metadata> {105 const { locale } = await params;106 return {107 alternates: {108 canonical: `https://example.com/${locale}`,109 languages: {110 'en': 'https://example.com/en',111 'es': 'https://example.com/es',112 'fr-CA': 'https://example.com/fr-ca',113 'x-default': 'https://example.com/',114 },115 },116 };117}118```119120### Astro121```astro122---123// src/layouts/BaseLayout.astro124const { currentLocale } = Astro;125---126<head>127 <link rel="canonical" href={Astro.url.href} />128 <link rel="alternate" hreflang="en" href="https://example.com/en" />129 <link rel="alternate" hreflang="es" href="https://example.com/es" />130 <link rel="alternate" hreflang="fr-CA" href="https://example.com/fr-ca" />131 <link rel="alternate" hreflang="x-default" href="https://example.com/" />132</head>133```134135### XML Sitemap Alternates136*(Best when pages have dozens of language variations to prevent HTML `<head>` bloat)*137```xml138<url>139 <loc>https://example.com/en</loc>140 <xhtml:link rel="alternate" hreflang="en" href="https://example.com/en" />141 <xhtml:link rel="alternate" hreflang="es" href="https://example.com/es" />142 <xhtml:link rel="alternate" hreflang="x-default" href="https://example.com/" />143</url>144```145146---147148## Common Mistakes & Anti-Patterns149150- **Using underscores (`en_US`):** Invalid. Hreflang strictly mandates hyphens (`en-US`).151- **Targeting language with a country code alone (`hreflang="uk"`):** `uk` is the Ukrainian language code, NOT the United Kingdom country code. For UK English, use `en-GB`.152- **Missing reciprocal links:** Forgetting to update older localized versions when launching a new language (e.g. adding French without updating English and Spanish to point to French).153- **Pointing hreflang to redirected URLs (301/302):** Alternate links must target the 200 OK canonical URL directly.154- **Translating URLs without translating content:** Creating shallow language shells that share 90% identical English copy results in soft 404 or duplicate content penalties.