Skip to content
← Back to Learn
International & Mobile Warning

Hreflang mistakes that break international targeting

Missing return tags, mismatched language codes, region-without-language, and other hreflang implementation bugs that quietly break geo-targeted indexing.

Hreflang tells Google which language and region version of a page to surface to which user. When it’s wrong, Google falls back to its own guess — usually serving the English version to non-English searchers, or the US version to EU users. The pages are still indexed; they just rank for the wrong queries in the wrong markets.

Why it matters

Hreflang is a clustering signal. If the cluster is malformed, Google doesn’t penalize — it ignores the hreflang entirely and picks a canonical version on its own. You end up with example.com/de/ competing against example.com/ for German users instead of being served in their place.

The four mistakes that account for most broken hreflang clusters:

  1. Missing return tags. Page A references page B; page B doesn’t reference page A. Google requires bidirectional confirmation.
  2. Wrong language codes. ISO 639-1 only. en-uk is invalid (it’s en-gb). cn is invalid (it’s zh).
  3. Region without language. Hreflang values must always include a language. hreflang="us" is invalid; use hreflang="en-us".
  4. Pointing to non-200 URLs. Targets that redirect, 404, or canonicalize elsewhere are silently dropped from the cluster.

How to detect it

Inspect the raw HTML or response headers — not the rendered DOM, since some crawlers don’t execute JS:

curl -s https://example.com/de/ | grep -i 'hreflang'

Then verify reciprocity. For every URL in the set, every other URL in the set must reference it back, including a self-reference:

<link rel="alternate" hreflang="en" href="https://example.com/" />
<link rel="alternate" hreflang="de" href="https://example.com/de/" />
<link rel="alternate" hreflang="fr" href="https://example.com/fr/" />
<link rel="alternate" hreflang="x-default" href="https://example.com/" />

The same four tags must appear on the English, German, AND French pages. Each page references the full cluster, including itself.

Google Search Console → Legacy tools → International Targeting reports cluster errors after crawl, but the report is rate-limited and lags by days. For a live audit, hreflang.org or any cluster validator gives instant reciprocity checks.

The fix

Universal HTML — in <head>

<link rel="alternate" hreflang="en" href="https://example.com/" />
<link rel="alternate" hreflang="en-gb" href="https://example.com/uk/" />
<link rel="alternate" hreflang="de" href="https://example.com/de/" />
<link rel="alternate" hreflang="de-at" href="https://example.com/at/" />
<link rel="alternate" hreflang="x-default" href="https://example.com/" />

Rules:

  • Use ISO 639-1 language codes (en, de, fr, zh) and ISO 3166-1 Alpha 2 region codes (gb, us, de, at).
  • Always pair region with language: en-gb, never gb alone.
  • Include x-default for the fallback when no language matches.
  • Absolute URLs only.

HTTP header — for non-HTML resources (PDFs, etc.)

Link: <https://example.com/doc-en.pdf>; rel="alternate"; hreflang="en",
      <https://example.com/doc-de.pdf>; rel="alternate"; hreflang="de"

XML sitemap — at scale

For sites with hundreds of localized URLs, the sitemap-based approach scales better than per-page tags:

<url>
  <loc>https://example.com/</loc>
  <xhtml:link rel="alternate" hreflang="en" href="https://example.com/" />
  <xhtml:link rel="alternate" hreflang="de" href="https://example.com/de/" />
</url>
<url>
  <loc>https://example.com/de/</loc>
  <xhtml:link rel="alternate" hreflang="en" href="https://example.com/" />
  <xhtml:link rel="alternate" hreflang="de" href="https://example.com/de/" />
</url>

Pick one method per cluster. Mixing tag-based and sitemap-based for the same URLs creates conflicting signals.

WordPress

WPML and Polylang both emit hreflang automatically once languages are configured. Yoast SEO Multilingual handles reciprocity by reading the translation graph. Verify the output — manual overrides in custom themes are the usual source of broken clusters.

Next.js (App Router)

export async function generateMetadata({ params }: Props): Promise<Metadata> {
  return {
    alternates: {
      languages: {
        "en": "https://example.com/",
        "de": "https://example.com/de/",
        "fr": "https://example.com/fr/",
        "x-default": "https://example.com/",
      },
    },
  };
}

Common pitfalls

  • en-uk is wrong. The country code for the United Kingdom is gb, not uk. This is the single most common hreflang bug.
  • zh vs zh-cn vs zh-tw. Use zh-cn (Simplified) and zh-tw (Traditional) — plain zh is ambiguous.
  • Pointing the cluster at canonical URLs that point elsewhere. If /de/ has <link rel="canonical" href="https://example.com/">, Google drops the German URL from the cluster entirely.
  • Forgetting the self-reference. Each page in the cluster must reference itself with its own hreflang.
  • Adding hreflang to noindexed pages. Google ignores hreflang signals from non-indexable URLs.
From diagnosis to deployment

Find the issue. Ship the fix.

Use Learn to understand the problem, then run Serpwise against your own site to see what can be approved and deployed.