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.
1. “No return tags” — page A links to B, but B doesn’t link back
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 ofzh-CNorzh-Hans(Google requires more specific codes for Chinese)hreflang="en_US"(underscore instead of hyphen — must been-US)hreflang="english"(full name instead of ISO code)hreflang="zh-CHS"(using legacy Microsoft locale code instead of ISOzh-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:
| Wrong | Right |
|---|---|
zh | zh-Hans (Simplified) or zh-Hant (Traditional) |
pt (ambiguous) | pt-BR or pt-PT |
en_US | en-US |
english | en |
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
Linkheader: 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
translationKeysource, 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-defaultpointing 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.
Related
- Canonical mismatch bilingual
- Hreflang warning quick guide
- Hreflang explained bilingual
- Bilingual pages drift apart
Tags: #Troubleshooting #SEO #Debug #hreflang