Search Console Hreflang Warning: Reading International Targeting Errors

Decoding the hreflang errors shown specifically in Google Search Console's International Targeting report — return tag missing, language code invalid, and how to act on each.

Search Console’s International Targeting report (under Legacy Tools) shows red warnings: “No return tags,” “Invalid language code,” “URL not found.” You shipped translations weeks ago and these errors persist. The frustrating part is that the report doesn’t tell you which specific page-pair triggered each warning — it just shows aggregate counts and an example URL. This guide decodes each error type and walks through how to fix them at scale.

For the general hreflang concept overview, see Hreflang warning — quick guide.

Common causes

Ordered by hit rate, highest first.

Hreflang must be bi-directional. If /en/article/ declares hreflang="zh" pointing to /zh/article/, then /zh/article/ MUST declare hreflang="en" pointing back. Asymmetric declarations are silently ignored by Google.

How to spot it:

# Check pair
curl -s https://site.com/en/article/ | grep -oE 'hreflang="[^"]+" href="[^"]+"'
curl -s https://site.com/zh/article/ | grep -oE 'hreflang="[^"]+" href="[^"]+"'

If /en/ has 4 hreflang lines but /zh/ has 1, the missing 3 cause “no return tags.”

2. “Invalid language code” — wrong format

Common mistakes:

  • hreflang="zh" instead of zh-CN or zh-Hans (Google requires more specific codes for Chinese)
  • hreflang="en_US" (underscore instead of hyphen — must be en-US)
  • hreflang="english" (full name instead of ISO code)
  • hreflang="zh-CHS" (using legacy Microsoft locale code instead of ISO zh-Hans)

How to spot it:

curl -s https://site.com/en/article/ | grep -oP 'hreflang="\K[^"]+' | sort -u

Validate each against valid ISO 639-1 codes and optionally + ISO 3166-1 region.

3. “URL not found” — hreflang target returns 404

You added a hreflang pointing to /de/article/ but the German version doesn’t exist yet, or its URL is different. The target returning 404 (or 301 elsewhere) fails the hreflang.

How to spot it:

for url in $(curl -s https://site.com/en/article/ | grep -oP 'hreflang="[^"]+" href="\K[^"]+'); do
  echo -n "$url: "
  curl -sI "$url" | head -1
done

Any non-200 response = broken hreflang.

4. Hreflang declares URLs that aren’t canonical

You point hreflang to /zh/article?ref=newsletter, but the canonical of that page is /zh/article/. Google treats this as a hreflang to a non-canonical URL and warns.

How to spot it: For each hreflang URL, check its canonical. Should match exactly.

5. Missing x-default

Not strictly an error, but warning-adjacent: without x-default, Google has no fallback for users whose locale doesn’t match any declared hreflang.

How to spot it: Check for <link rel="alternate" hreflang="x-default" href="..." />. If absent, add it pointing to your primary language version.

6. Hreflang exists in both <head> and HTTP header

You added hreflang in HTML <link> tags AND as HTTP Link headers (or in sitemap). Google reads all three but conflicts cause warnings.

How to spot it:

curl -sI https://site.com/en/article/ | grep -i link:

If both HTML <link rel="alternate"> and HTTP Link headers are emitting hreflang, pick one (HTML is most common).

Shortest path to fix

Step 1: Generate hreflang from a single source

Don’t write hreflang manually per page. Generate from a translation-key map:

// site.config.mjs or similar
export const locales = {
  en: { hreflang: 'en', urlPrefix: '/en/' },
  zh: { hreflang: 'zh-Hans', urlPrefix: '/zh/' },
};

export function hreflangAlternates(translationKey) {
  return Object.entries(locales).map(([lang, { hreflang, urlPrefix }]) => ({
    hreflang,
    href: `https://site.com${urlPrefix}${translationKey}/`,
  })).concat([{ hreflang: 'x-default', href: `https://site.com/en/${translationKey}/` }]);
}

In your layout, iterate this list to emit <link rel="alternate"> tags. One source of truth means no asymmetry.

Step 2: Use full locale codes

For most CJK and regional variants, prefer the explicit forms:

WrongRight
zhzh-Hans (Simplified) or zh-Hant (Traditional)
pt (ambiguous)pt-BR or pt-PT
en_USen-US
englishen

ISO 639-1 + optional ISO 3166-1 region. No spaces, no underscores.

Step 3: Validate all hreflang URLs return 200

# CI script — extract all hreflang URLs from sitemap and verify each
xmllint --xpath '//*[local-name()="link"]/@href' sitemap.xml | grep -oP 'href="\K[^"]+' | while read url; do
  status=$(curl -s -o /dev/null -w "%{http_code}" "$url")
  [ "$status" = "200" ] || echo "BAD: $status $url"
done

Step 4: Add x-default

Always include:

<link rel="alternate" hreflang="x-default" href="https://site.com/en/article/" />

Points to your primary / fallback language version.

Step 5: Pick one delivery mechanism

Pick exactly one:

  • HTML <link> tags in <head> — easiest for static sites
  • HTTP Link header: best for non-HTML resources (PDFs, etc.)
  • Sitemap <xhtml:link>: good for very large sites

Don’t use all three. Pick one and stick with it.

Step 6: Submit sitemap and wait

After fix, regenerate sitemap and resubmit in Search Console → Sitemaps. The International Targeting report updates over 1-3 weeks.

Step 7: Validate with a tool

Run your URLs through hreflang validator or Aleyda Solis’s tool. They show exactly which page-pairs are asymmetric.

Prevention

  • Generate hreflang from a single translationKey source, never hand-write per page.
  • Validate hreflang in CI: every URL must return 200, every code must be valid, every pair must be symmetric.
  • Always include x-default pointing to your primary language version.
  • Pick one delivery mechanism (HTML link tags is most common) and don’t mix.
  • When adding a new locale, audit existing pages to ensure they emit hreflang for the new locale.

Tags: #Troubleshooting #SEO #Debug #hreflang