International SEO and Hreflang
International SEO is an area where the gap between "implemented" and "implemented correctly" is exceptionally large. Industry data puts the error rate at around 75% — three out of four international sites have hreflang configurations that are either broken, incomplete, or actively counterproductive. For a signal that directly determines which language and regional variant of your content users in each market see, that error rate has real business consequences.
Done right, international SEO can generate 40–60% of total traffic from non-primary markets within 18 months of implementation. Done wrong, you serve German users English pages, split domain authority across competing language variants, and leave Google with contradictory signals it resolves in unpredictable ways.
What Hreflang Actually Does
The hreflang attribute tells search engines two things: that multiple versions of a page exist for
different languages or regions, and which version to serve to users in each locale. Without it,
Google picks whatever version it thinks is most relevant for each user — often the version with the
most links, which is usually your primary-language version, served to everyone.
With a correct hreflang cluster, a German user searching on Google.de sees the German version of your content in results, not the English version. A Spanish speaker in Mexico and a Spanish speaker in Spain can be served different variants if you maintain them (es-MX vs es-ES). And the authority signals from links pointing to any version in the cluster are appropriately shared rather than fragmented.
The key properties of a valid hreflang implementation:
- Every page in the cluster must declare all other pages — the relationship must be complete
- Self-referencing hreflang is required — each page must include itself in its own hreflang declaration
- Reciprocation is mandatory — if page A declares page B, page B must declare page A
- Include x-default — a catch-all for users who don't match any specific locale
HTML Head Implementation
The most common implementation approach places hreflang tags in the <head> of each page:
<!-- English version: example.com/about -->
<head>
<link rel="alternate" hreflang="en" href="https://example.com/about" />
<link rel="alternate" hreflang="de" href="https://example.com/de/ueber-uns" />
<link rel="alternate" hreflang="fr" href="https://example.com/fr/a-propos" />
<link rel="alternate" hreflang="es-MX" href="https://example.com/es-mx/acerca-de" />
<link rel="alternate" hreflang="es-ES" href="https://example.com/es/acerca-de" />
<link rel="alternate" hreflang="x-default" href="https://example.com/about" />
</head>
<!-- German version: example.com/de/ueber-uns — must reciprocate -->
<head>
<link rel="alternate" hreflang="en" href="https://example.com/about" />
<link rel="alternate" hreflang="de" href="https://example.com/de/ueber-uns" />
<link rel="alternate" hreflang="fr" href="https://example.com/fr/a-propos" />
<link rel="alternate" hreflang="es-MX" href="https://example.com/es-mx/acerca-de" />
<link rel="alternate" hreflang="es-ES" href="https://example.com/es/acerca-de" />
<link rel="alternate" hreflang="x-default" href="https://example.com/about" />
</head>
Notice that both pages declare the complete set — neither omits itself. The German page doesn't just
declare hreflang="de" for itself; it declares the full cluster. This is the most common error:
pages that only declare their own locale or only declare their locale plus the default, rather than
the full symmetric set.
Generating this programmatically in Next.js:
// lib/hreflang.ts
export function generateHreflang(
locales: string[],
getLocalizedPath: (locale: string) => string,
defaultLocale: string,
): Metadata["alternates"] {
const languages: Record<string, string> = {};
for (const locale of locales) {
languages[locale] = getLocalizedPath(locale);
}
return {
canonical: getLocalizedPath(defaultLocale),
languages: {
...languages,
"x-default": getLocalizedPath(defaultLocale),
},
};
}
// app/[locale]/about/page.tsx
export async function generateMetadata({
params,
}: {
params: { locale: string };
}): Promise<Metadata> {
return {
alternates: generateHreflang(
["en", "de", "fr", "es-MX", "es-ES"],
(locale) => `https://example.com/${locale}/about`,
"en",
),
};
}
Sitemap-Based Implementation
For large sites, declaring hreflang in the HTML head of every page adds significant markup overhead. The XML sitemap provides an alternative that concentrates all hreflang declarations in one place:
<?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/about</loc>
<xhtml:link rel="alternate" hreflang="en"
href="https://example.com/about"/>
<xhtml:link rel="alternate" hreflang="de"
href="https://example.com/de/ueber-uns"/>
<xhtml:link rel="alternate" hreflang="x-default"
href="https://example.com/about"/>
</url>
<url>
<loc>https://example.com/de/ueber-uns</loc>
<xhtml:link rel="alternate" hreflang="en"
href="https://example.com/about"/>
<xhtml:link rel="alternate" hreflang="de"
href="https://example.com/de/ueber-uns"/>
<xhtml:link rel="alternate" hreflang="x-default"
href="https://example.com/about"/>
</url>
</urlset>
The same completeness rules apply: each <url> entry must declare the full set of alternates,
including itself. Google accepts all three implementation methods (HTML head, HTTP headers, XML
sitemap) and treats them equivalently.
URL Structure Strategy
There are three URL structure options for international sites, and the choice has significant domain authority implications:
Subdirectories (recommended for most teams): example.com/de/, example.com/fr/ All language
variants live under the same domain, sharing domain authority. A link to example.com from an
external site contributes to the authority of all language variants. This is generally the right
choice for teams that want to consolidate SEO equity and have the content management infrastructure
to serve multiple language paths.
Subdomains: de.example.com, fr.example.com Google treats subdomains as separate entities for
most purposes. Links to example.com don't automatically benefit de.example.com. Some large
international operations use subdomains for operational reasons (separate infrastructure, separate
teams), but from a pure SEO standpoint, subdirectories are preferable.
Country-code TLDs (ccTLDs): example.de, example.fr The strongest geotargeting signal —
Google knows exactly what country a .de domain targets. But it comes with the highest operational
cost (managing multiple domains, building separate domain authority for each) and no cross-domain
equity sharing. ccTLDs make sense for very large organizations where country-specific brand identity
and operational independence justify the cost.
Hreflang vs. User Routing
A critical distinction that causes implementation confusion: hreflang is a signal to search engines about which version to surface in search results. It has nothing to do with what happens when a user visits your site.
User routing — detecting the user's location via IP or Accept-Language headers and redirecting them
to their locale's version — is a separate system that operates in parallel. A German user following
a Google.de search result to example.com/de/ueber-uns should not be automatically redirected away
from that URL just because their browser is set to French. They arrived at that URL intentionally.
The common implementation mistake is coupling hreflang with geolocation redirects, creating a situation where users who follow localized search results are immediately redirected away from them. This breaks the hreflang signal and frustrates users who found a specific language version they wanted.
The pattern to follow: geolocation redirects should apply only to users who arrive at the root domain or a language-agnostic entry point, not to users who land directly on a localized URL. Let users stay where they land.
Common Errors and How to Detect Them
The most frequent hreflang errors:
- Missing reciprocation: Page A declares page B, but page B doesn't declare page A. Google ignores the relationship entirely.
- Missing self-reference: A page declares its alternates but omits itself.
- Missing x-default: Users in markets not covered by a specific locale get served an inconsistent or unexpected result.
- Return code errors: A hreflang URL returns 404 or redirects. The entire cluster's signals are weakened.
- Language code errors: Using
en-ukinstead ofen-GB, orzhwhen you meanzh-Hans(Simplified Chinese) orzh-Hant(Traditional Chinese). Hreflang uses BCP 47 language tags.
The fastest way to detect these is Google Search Console's International Targeting report, which flags hreflang errors at the cluster level. Screaming Frog can crawl your site and validate hreflang completeness programmatically — it's well worth running after any hreflang change before deploying to production.