hreflang mistakes that send users to the wrong country (and how to catch them)
hreflang tells search engines which language or regional version of a page to show to which users. When it works, German searchers land on /de/ and British ones on /uk/. When it breaks, nothing errors and no page drops out of the index. Users just quietly land on the wrong version, bounce, and convert worse. It's one of the most fragile parts of international SEO because it lives in templates that get touched constantly.
<link rel="alternate" hreflang="en-gb" href="https://example.com/uk/pricing">
<link rel="alternate" hreflang="de-de" href="https://example.com/de/preise">
<link rel="alternate" hreflang="x-default" href="https://example.com/pricing">
The mistakes that actually matter
1. Tags dropped by a template or plugin change
A redesign, a new layout component or an SEO plugin update stops outputting the alternates on some templates. Search engines fall back to guessing, often wrongly.
2. Missing return links
hreflang only counts when pages reference each other. If /uk/pricing lists /de/preise but the German page doesn't list the British one back, Google ignores the pair.
3. Alternates pointing at the wrong host
Absolute URLs built from a configured base URL end up pointing at staging, an old domain, or http://. It's the same root cause as canonicals pointing to staging.
4. Alternates that redirect, 404 or are noindexed
Each hreflang URL should return 200, be indexable and be self-canonical. Pointing to a URL that redirects (for example, after a URL-structure change) or to a noindexed page undermines the whole cluster.
5. Invalid codes
Language codes are ISO 639-1 (en, de), optionally followed by an ISO 3166-1 alpha-2 region (en-gb, not en-uk). A region on its own (gb) is invalid.
6. Conflicts with canonicals
If every language version canonicalises to the English page, you're telling Google the other versions are duplicates, and hreflang can't override that. Each version should be self-canonical.
How to check
- List the alternates on a page:
curl -s https://example.com/uk/pricing | grep -io '<link[^>]*hreflang[^>]*>'. Remember they can also be set in HTTPLinkheaders or in the XML sitemap. - Follow each URL and confirm it returns 200, isn't noindexed, is self-canonical, and lists the original page back.
- Check the codes against the format above, and include an
x-defaultfor users who match none of them. - Spot-check in Search Console: URL Inspection shows the canonical Google picked. A different-language URL there is a red flag.
The free SEO config check covers status, noindex and canonical for each alternate URL you paste in.
Keeping it from coming back
- Generate hreflang from one source of truth (your locale config or CMS), not per template.
- Add a release check that one URL per locale still outputs the full set.
- Monitor it: hreflang breaks during unrelated work, so you want to know the hour a template stops outputting it, not at the next quarterly audit.