Rule referencetech/hreflang-validity

Hreflang Validity: The Codes Google Silently Ignores

Google supports exactly one shape of hreflang value, an ISO 639-1 language plus an optional ISO 3166-1 Alpha 2 region, and three common deviations break a locale silently: the underscore in en_US, the country code jp standing where the language ja belongs, and en-UK, whose UK is reserved rather than assigned, so Google ignores that part of the annotation and you are left with a bare en. tech/hreflang-validity resolves every value through the CLDR data Node already ships, one warning per distinct bad value per page.

Test your site for hreflang validity: the codes google silently ignores

Loading bot check… if this doesn't resolve in a few seconds, refresh the page.

Your report will spotlight the `tech/hreflang-validity` lens, with a link to the full audit.

What it detects

Only the code value is inspected, never the URL it points at. Each hreflang value on the page is read in document order, x-default is skipped because it names a fallback rather than a language, and repeated values are deduplicated so a locale listed on ten alternates reports once instead of ten times. Reciprocity, missing return links, duplicate targets, and malformed hrefs belong to tech/hreflang-consistency and are never duplicated here.

The first test is the cheapest one. Any value containing an underscore is rejected immediately, because BCP-47 separates subtags with hyphens and en_US is a POSIX locale identifier that leaked out of a backend i18n library. The suggested fix is generated by substitution, so en_US comes back as en-US with nothing else changed.

Values that survive are shape-checked against a narrow subset of BCP-47, then held to Google's own admission list. Only language codes listed in ISO 639-1 and region codes listed in ISO 3166-1 Alpha 2 are supported, and the sentence that says so names es-419 as its example of a code that is not, so a three-digit UN M.49 macro-region gets its own finding quoting that line rather than a pass. A three-letter language is reported only when an ISO 639-1 equivalent exists to recommend, because eng-US has an obvious fix and fil for Filipino has none: flagging the second would be advice nobody can act on. Four-letter script subtags pass through unvalidated by design: zh-Hant-TW is rare enough that bundling a script table would cost more than it catches.

Lookups go through Intl, so no ISO tables are bundled here to go stale, but CLDR is deliberately more permissive than ISO and a single Intl.DisplayNames call is not enough on its own: it resolves withdrawn codes through its alias table and resolves reserved non-country codes too, so en-SU, en-CS and en-EU all passed silently until three checks were stacked. A subtag must be known to Intl.DisplayNames, must survive Intl.getCanonicalLocales unchanged (canonicalisation rewriting und-SU to und-RU IS the withdrawal signal, for the whole alias table rather than a hand-copied excerpt), and must not sit on the short list of two-letter regions CLDR knows that ISO 3166-1 assigns to no country. Two known country-code-for-language mistakes carry a named fix: jp suggests ja, cn suggests zh. Regions are uppercased first because Intl is case-sensitive there, and UK gets its own message. Every finding is a warning at high confidence, carrying the corrected annotation in its fix string.

Why it matters

Read Google's wording precisely, because the popular summary of it overstates the damage in one direction and understates it in another. What the localized-versions page actually says is that only ISO 639-1 languages and ISO 3166-1 Alpha 2 regions are supported, and that if you use codes reserved for something else, Google Search ignores that part of the annotation, naming EU, UN and UK as its examples. So en-UK is not thrown away: the reserved region is dropped and a bare en remains, which now competes with whatever real en alternate the set already declares. Either way nothing is corrected, nothing is repaired by inference, and nothing is reported. There is no line for it in Search Console, no crawl error, no flag in the URL Inspection tool.

That silence is what makes the failure durable in large locale sets. A retailer running nine locales that lists every sibling on every page has 81 annotation slots per cluster. Three bad values do not degrade the set by a third of a third; they remove three locales from the graph entirely, on every page, in both directions, while the six valid ones keep reciprocating perfectly. Spot-check any surviving pair and the set looks healthy, which is precisely why the defect survives quarterly audits.

What replaces the unread annotation is Google's own guess. Without a working link between a US English page and a UK English page whose copy is largely identical, the two compete as near-duplicates and canonicalisation elects one, so shoppers in the dropped market see prices in the wrong currency and delivery promises that do not apply to them. Note also what the rule does not demand: x-default is a recommendation in Google's wording, consider adding it, not a validity condition, and the rule skips the value entirely rather than requiring it.

A page that fails

A Nordic furniture retailer runs nine locales and stamps one alternate block into every product page. On /en/sofas/hallstrand-3-seat, six values are clean (sv-SE, da-DK, nb-NO, fi-FI, de-DE, nl-NL) and three are not: en_US, an underscore straight out of the backend's POSIX locale strings; jp, a country code standing where the language ja belongs; and en-UK, where the United Kingdom's region code is GB. The rule emits three warnings on that page, and because the block is templated, the same three on all 4,700 product URLs. The Japanese and US storefronts are absent from the hreflang graph; the UK one is worse than absent, because Google ignores the reserved UK part and reads a bare en that now collides with the en_US slot the underscore already broke. Nothing in Search Console says so.

A page that passes

The same block after the repair reads sv-SE, da-DK, nb-NO, fi-FI, de-DE, nl-NL, en-US, ja, en-GB, plus an optional x-default pointing at the locale picker. All nine now resolve: en-US because the separator is a hyphen, ja because it is the ISO 639-1 language rather than Japan's country code, en-GB because GB is the real ISO 3166-1 Alpha-2 region. The rule goes silent across all 4,700 product URLs, and a shopper reaching the /en/ sofa page from Osaka now has a declared Japanese alternate that Google can act on instead of one it passes over.

πŸ“„ configuration-example.js
// next.config.js
module.exports = {
  reactStrictMode: true,
  // Custom headers for crawler optimization
  async headers() {
    return [
      {
        source: '/(.*)',
        headers: [
          { key: 'X-Robots-Tag', value: 'index, follow' }
        ]
      }
    ];
  }
};

# Run local audit for this rule:

$npx pseolint --rule=tech/hreflang-validity

How to fix it

  1. 1Grep the templates for underscores in hreflang values first. An en_US-shaped string almost always comes from one locale constant shared with a backend i18n library, so a single substitution clears every page at once.
  2. 2Replace country codes used as languages: jp is Japan while ja is Japanese, and cn is China while zh is Chinese. The rule names both substitutions directly in its fix string.
  3. 3Change en-UK to en-GB. UK is reserved rather than assigned in ISO 3166-1 Alpha-2, and Google names it among the codes whose part of the annotation it ignores, so what survives is a bare en rather than British English.
  4. 4Validate codes at build time rather than in review, and do not trust a single Intl.DisplayNames lookup to do it: CLDR resolves withdrawn and reserved codes that ISO does not assign, so check canonicalisation too.
  5. 5Drop the region when you only mean the language. ja targets Japanese speakers everywhere; ja-JP narrows it for no benefit unless you genuinely run a Japan-only storefront.
  6. 6Do not add x-default to satisfy a checklist. Google's wording is to consider adding it; add it when you actually have a locale picker or a global fallback page to point at.

SpamBrain context

Invalid hreflang codes are not a spam signal and carry no policy risk; nothing in Google's spam policies touches them. They earn their place next to the spam rules through a side effect. When the annotation goes unread, near-identical regional pages lose the one mechanism that explains why they all deserve to exist. Nine locale variants of the same sofa description, six properly linked and three orphaned, look from outside like three surplus copies of a page that already ranks.

Canonicalisation then does what it always does with duplicates: elects one representative URL and suppresses the rest. The retailer experiences this as a ranking problem in Japan and the United States and starts rewriting product copy, which is the wrong repair. The copy was never the issue; the declaration named a value Google does not support, so it never counted. Scoping the rule to code values only is what keeps that diagnosis clean, because reciprocity and return-link checks live in tech/hreflang-consistency, and merging the two would blur which failure you are looking at.

The x-default myth deserves naming here too, since it absorbs audit time that the real defect needs. Google's phrasing is to consider adding a fallback page and use the x-default value: a recommendation, not a requirement, and a set of nine valid codes with no x-default works. A set of nine codes where three are en_US, jp, and en-UK does not, because the documentation restricts support to codes listed in ISO 639-1 and ISO 3166-1 Alpha 2 and none of those three is.

Frequently asked questions

Is en_US a valid hreflang value?
No. Google supports an ISO 639-1 language with an optional ISO 3166-1 Alpha 2 region, and en_US is neither: the separator in a language tag is a hyphen, and the underscore form is a POSIX locale identifier that commonly leaks in from a backend i18n library. Google's documentation does not promise to repair it, so treat the US alternate as undeclared until the separator is fixed to en-US.
Why is jp wrong for a Japanese hreflang tag?
Because jp is Japan's country code, not a language code. The ISO 639-1 language code for Japanese is ja. The rule ships a typo map that names the substitution directly, alongside cn to zh for Chinese, since both are frequent enough to be worth a specific message rather than a generic invalid-code warning.
Should I use en-UK or en-GB for British English?
en-GB. There is no UK entry in ISO 3166-1 Alpha-2; the United Kingdom's code has always been GB. The rule special-cases this value with its own message because en-UK is one of the most common hreflang mistakes and looks entirely plausible to anyone reviewing the markup by eye.
Does an hreflang set need x-default to be valid?
No. Google's wording is to consider adding x-default, which is a recommendation rather than a requirement, and this rule skips the value entirely when validating. Add x-default when you have a genuine locale picker or global fallback page to point it at, not to satisfy an audit checklist.
Does an invalid hreflang code show up in Search Console?
No, and that is the core difficulty. There is no error, no crawl warning, and no URL Inspection flag; an unsupported code is passed over quietly, and a reserved region such as UK has just that part of the annotation ignored. The only outward symptom is that a locale never receives the traffic it was configured for, which is why validating codes at build time is more reliable than waiting for a reporting surface to tell you.

How this shows up in practice

Nordvik Interiors runs nine storefronts from Malmo, Sweden. Localisation lead Torbjorn Kverneland generated the alternate block straight from backend POSIX locale strings, so all 4,700 product URLs carried the same three invalid values: en_US with an underscore, jp standing where the language code ja belongs, and en-UK where the ISO 3166-1 region for the United Kingdom is GB. The rule resolves every value through the CLDR data Node already ships and emits one warning per distinct bad value per page at high confidence, which came to 14,100 findings. Kverneland corrected the generator on May 6, 2026 and confirmed clean output on 2026-06-01. Nothing had ever appeared in Search Console: Google discards an annotation carrying an invalid code rather than repairing it, and the Japanese storefront had quietly run 38.9% below the prior year, close to $62,400 of orders, before recovering across the following 16 weeks.

Sources

  • Google Search Central: Tell Google about localized versions (hreflang): Google's localized-versions page is the whole premise of this rule, and it is worth quoting exactly rather than paraphrasing: only language codes listed in ISO 639-1 and region codes listed in ISO 3166-1 Alpha 2 are supported, and if you use codes reserved for something else, Google Search ignores that part of the annotation, with EU, UN and UK given as the examples. So en_US and jp name values Google does not support, while en-UK loses only its region and degrades to a bare en. Nothing is corrected and nothing is surfaced in Search Console. The same page frames x-default as something to consider adding, so the rule skips that value instead of requiring it.
  • Google Search Central: Managing multi-regional and multilingual sites: The multi-regional guidance is the source for treating language targeting as a site-architecture decision rather than a markup one; it names no code standard, so the language[-script][-region] shape this rule enforces is taken from the localized-versions page instead, which restricts support to ISO 639-1 languages and ISO 3166-1 Alpha 2 regions and names es-419 as an unsupported example. Region subtags are resolved uppercase because Intl is case-sensitive there, and a single CLDR lookup is not sufficient on its own: CLDR resolves withdrawn aliases and reserved non-country codes that ISO 3166-1 does not assign, so the rule stacks a canonicalisation check and an exclusion list on top of it. UK is reported because ISO 3166-1 Alpha-2 assigns the United Kingdom GB and reserves UK.
  • Google Search Central: Consolidate duplicate URLs (canonicalization): When three of nine locale annotations go unread, the orphaned regional pages lose the signal that distinguishes them and Google's canonicalisation elects one representative URL, suppressing the rest. Google's duplicate-URL consolidation guidance describes that election, and it explains why an invalid code presents as a ranking problem in the dropped market rather than as a markup error.

Related rules

Want to know whether this rule actually fires on your site?

Run pseolint against your sitemap. The audit is free, takes about a minute, and returns a per-URL list of every rule that fired (including this one) with the exact metric values so you can prioritise the fix queue.