By SitemapFixer Team
Updated April 2026

rel="alternate" hreflang: Implementation Guide for Devs

Validate hreflang across your sitemapScan hreflang free

The <link rel="alternate" hreflang="..."> tag is how you tell Google and Yandex which language and regional version of a page to serve to a given user. Implemented correctly, it removes duplicate-content concerns across translations and lifts CTR by serving the right locale in SERPs. Implemented incorrectly — and most implementations have at least one bug — it does nothing, or worse, causes Google to swap the wrong page into search results. This guide covers the syntax, the three valid declaration methods, the rules you cannot break, and the mistakes that quietly break almost every hreflang setup we audit.

The Syntax

Every hreflang annotation has three parts: rel="alternate" (constant), hreflang="lang-region" (the language and optional region code), and href="URL" (the absolute URL of the alternate version). The minimal valid form looks like this:

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

The language code can stand alone (en, es, de) or be combined with a region code (en-US, en-GB, es-MX). When you include a region, you are telling Google to serve that version specifically to users in that country, not to all users speaking that language. Use region codes only when you actually have region-specific content (different prices, different shipping, localized terminology). For a generic English page that serves all English speakers, use en alone.

Valid Language and Region Codes

Language codes follow ISO 639-1 (two-letter): en, es, fr, de, zh, ja, pt, ru, ar. Region codes follow ISO 3166-1 Alpha 2: US, GB, CA, AU, MX, BR, CN, JP. The format is always language-REGION, joined by a hyphen, with the language lowercase and the region uppercase (Google is case-insensitive but stick to the convention). Common errors to avoid:

The United Kingdom is GB, not UK. en-UK is invalid and will be ignored. The correct code is en-GB.

Latin America has no single code. If you serve a Spanish version for all of Latin America, use es-419 (the UN M.49 region code for Latin America) or, more commonly accepted, fall back to plain es and let Google pick.

Chinese needs script disambiguation. Simplified Chinese is zh-Hans, Traditional is zh-Hant. You can also pair with region: zh-CN, zh-TW, zh-HK. Pick one convention and stick with it across your site.

Norwegian, Hebrew, Indonesian have legacy codes. Norwegian is no or nb (Bokmål) / nn (Nynorsk). Hebrew is he, not iw (the legacy ISO code). Indonesian is id, not in. Validate against the current ISO 639-1 list, not what you remember.

x-default: The Fallback

The x-default hreflang value tells Google which version to serve when no language match exists for the user. It is the default for users whose language is not in your set, or for users whose language detection is ambiguous. Most sites use their primary English page as the x-default, or a language-selector landing page that asks the user to pick.

<head>
  <link rel="canonical" href="https://example.com/" />
  <link rel="alternate" hreflang="en" href="https://example.com/" />
  <link rel="alternate" hreflang="es" href="https://example.com/es/" />
  <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/" />
</head>

x-default is technically optional, but Google strongly recommends including it. Without one, users outside your declared language set may get whichever version Google guesses — usually based on the strongest hreflang cluster, which can be unpredictable. Always set x-default explicitly.

The Return-Tag Rule

This is the rule most sites get wrong. If page A links to page B with hreflang, page B must link back to page A with hreflang. All hreflang declarations are bidirectional. If they are not, Google ignores the entire cluster.

This means every page in a language cluster must list every other page in the cluster, including itself. A 5-language site has every page outputting 5 hreflang tags (one per language) plus an x-default — that is 6 tags on every page, and they must be identical across all 5 versions except for the URL of the page itself.

Practical consequence: hreflang management does not scale well via hand-edited templates. Once you exceed about 3 languages, you need a generated cluster — a single source of truth that emits all hreflang tags for every version, so adding a new language only requires updating one config rather than every page in every translation.

The Three Declaration Methods (Pick One)

Google accepts hreflang declarations in three places. You must pick exactly one method per URL. Mixing methods on the same URL produces unpredictable behavior — Google may pick one, may pick neither, may flag the cluster as broken in GSC.

Method 1: HTML head tags. The most common and easiest for HTML pages. Pros: trivial to implement in any CMS or framework, easy to inspect via view-source, debuggable in Rich Results Test. Cons: bloats every page's HTML (n languages = n tags on every page), does not work for non-HTML files (PDFs, images), can balloon page weight on sites with 20+ language versions.

Method 2: HTTP Link header. The only valid method for non-HTML files. Required when you need hreflang on PDFs, downloadable docs, or other resources that have no <head>.

# nginx config — adds hreflang Link header to a PDF
location = /docs/whitepaper-en.pdf {
  add_header Link '<https://example.com/docs/whitepaper-en.pdf>; rel="alternate"; hreflang="en", <https://example.com/docs/whitepaper-es.pdf>; rel="alternate"; hreflang="es", <https://example.com/docs/whitepaper-de.pdf>; rel="alternate"; hreflang="de", <https://example.com/docs/whitepaper-en.pdf>; rel="alternate"; hreflang="x-default"';
}

# Verify with curl
curl -I https://example.com/docs/whitepaper-en.pdf | grep -i link

Method 3: XML sitemap. The most scalable for large multilingual sites. You declare each URL group once in the sitemap, with all language alternates as xhtml:link children. No tags in the page HTML at all. Pros: zero page-weight cost, single source of truth, easiest to maintain across hundreds of pages. Cons: harder to debug (you cannot see hreflang in view-source), most CMS plugins do not generate hreflang sitemaps natively, requires the xmlns:xhtml namespace declaration.

<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
        xmlns:xhtml="http://www.w3.org/1999/xhtml">
  <url>
    <loc>https://example.com/</loc>
    <xhtml:link rel="alternate" hreflang="en" href="https://example.com/" />
    <xhtml:link rel="alternate" hreflang="es" href="https://example.com/es/" />
    <xhtml:link rel="alternate" hreflang="de" href="https://example.com/de/" />
    <xhtml:link rel="alternate" hreflang="x-default" href="https://example.com/" />
  </url>
  <url>
    <loc>https://example.com/es/</loc>
    <xhtml:link rel="alternate" hreflang="en" href="https://example.com/" />
    <xhtml:link rel="alternate" hreflang="es" href="https://example.com/es/" />
    <xhtml:link rel="alternate" hreflang="de" href="https://example.com/de/" />
    <xhtml:link rel="alternate" hreflang="x-default" href="https://example.com/" />
  </url>
  <url>
    <loc>https://example.com/de/</loc>
    <xhtml:link rel="alternate" hreflang="en" href="https://example.com/" />
    <xhtml:link rel="alternate" hreflang="es" href="https://example.com/es/" />
    <xhtml:link rel="alternate" hreflang="de" href="https://example.com/de/" />
    <xhtml:link rel="alternate" hreflang="x-default" href="https://example.com/" />
  </url>
</urlset>

Note that even when using sitemap-based hreflang, every URL in the cluster must appear as its own <url> entry, each with the full set of alternates. The return-tag rule applies in sitemaps too. See our hreflang sitemap guide for production examples.

hreflang and Canonical Interaction

This is where most hreflang implementations silently fail. The rule: each language version must have a self-referencing canonical pointing to itself, never cross-language. Hreflang and canonical are not interchangeable. Canonical says "this is the master copy of this content." Hreflang says "these are the language alternates of this content." If your Spanish page canonicals to the English page, you have told Google to drop the Spanish page from the index — and the hreflang cluster collapses with it.

<!-- CORRECT: on https://example.com/es/ -->
<link rel="canonical" href="https://example.com/es/" />
<link rel="alternate" hreflang="en" href="https://example.com/" />
<link rel="alternate" hreflang="es" href="https://example.com/es/" />
<link rel="alternate" hreflang="de" href="https://example.com/de/" />
<link rel="alternate" hreflang="x-default" href="https://example.com/" />

<!-- WRONG: cross-language canonical breaks hreflang -->
<link rel="canonical" href="https://example.com/" />
<link rel="alternate" hreflang="es" href="https://example.com/es/" />

For more depth on the interaction, see our hreflang and canonical guide and the self-referencing hreflang rule.

Common Wrong Patterns

These are the patterns we see break hreflang implementations most often:

<!-- WRONG 1: invalid region code -->
<link rel="alternate" hreflang="en-UK" href="..." />  <!-- should be en-GB -->

<!-- WRONG 2: underscore instead of hyphen -->
<link rel="alternate" hreflang="en_US" href="..." />  <!-- should be en-US -->

<!-- WRONG 3: relative URL -->
<link rel="alternate" hreflang="es" href="/es/" />  <!-- must be absolute -->

<!-- WRONG 4: only language code on a region-targeted page -->
<link rel="alternate" hreflang="en" href="https://example.com/us/" />
<link rel="alternate" hreflang="en" href="https://example.com/gb/" />  <!-- duplicate hreflang, both ignored -->

<!-- WRONG 5: missing self-reference -->
<!-- on https://example.com/es/ -->
<link rel="alternate" hreflang="en" href="https://example.com/" />
<link rel="alternate" hreflang="de" href="https://example.com/de/" />
<!-- no hreflang="es" pointing to self — cluster broken -->

<!-- WRONG 6: hreflang to a redirecting URL -->
<link rel="alternate" hreflang="es" href="https://example.com/es" />
<!-- but /es 301-redirects to /es/ — Google drops it -->

<!-- WRONG 7: hreflang to a noindex page -->
<link rel="alternate" hreflang="es" href="https://example.com/es/" />
<!-- but /es/ has <meta name="robots" content="noindex"> — cluster broken -->

Other frequent mistakes: typos in language codes (sp for Spanish — it's es; jp for Japanese — it's ja), country codes used as language codes (us, uk are not valid hreflang values on their own), and mixing methods (HTML head tags on some pages, sitemap hreflang on others, with overlap). See our hreflang errors guide for a fuller catalogue.

Debugging hreflang

Google Search Console's old International Targeting report was deprecated in 2022, but it is still accessible via the legacy URL https://search.google.com/search-console/legacy-tools/international-targeting for properties that had it. It surfaces "no return tags" errors per URL and is still useful when it works — but Google has been progressively disabling it, so do not rely on it long term.

Rich Results Test and URL Inspection. The Rich Results Test (now part of URL Inspection in GSC) shows the rendered HTML and any link tags Google detected. Run a URL through it, expand the rendered HTML, and confirm your hreflang tags appear in the head exactly as written. If they are missing, your CMS or framework is stripping them — check rendering pipelines, content security policies, or async injection bugs.

Crawler-based audits. Ahrefs Site Audit, Sitebulb, and Screaming Frog all flag hreflang issues at scale. Sitebulb has the cleanest reporting (a dedicated "Hreflang" tab with cluster visualization). Ahrefs flags "hreflang to non-canonical", "missing return tag", and "hreflang conflicts with canonical" as separate issue types — set up a recurring audit and treat any new appearance as a regression. Screaming Frog needs the "Configuration → Spider → Crawl → hreflang" option enabled, then exposes hreflang validation under the Hreflang tab.

Manual spot check. Pick a URL, view-source, copy the hreflang tags. Then visit each URL listed in those tags and confirm it has the same hreflang block, with the right URL self-referencing and the original URL listed back. If any one page in the cluster is missing or wrong, the entire cluster fails. For more on tracking down issues at scale, see our international duplicate content guide.

Implementation Checklist

Before you ship a hreflang setup, verify each of these:

1. Every language version has a self-referencing hreflang tag pointing to itself.

2. Every page lists every other language in the cluster (return-tag rule).

3. All language codes are valid ISO 639-1 + 3166-1 (no en-UK, no sp, no jp).

4. All href values are absolute URLs, not relative paths.

5. All href values return 200 OK (not 301, not 404, not noindex).

6. Each page's canonical points to itself, never to another language.

7. Exactly one declaration method is used per URL (HTML head OR HTTP header OR sitemap).

8. An x-default entry is present.

9. The cluster is verified end-to-end via URL Inspection or Rich Results Test on at least one URL per language.

10. A monthly crawler audit (Ahrefs, Sitebulb, or SitemapFixer) is scheduled to catch regressions.

Hreflang is fragile because every page in the cluster is a dependency of every other page. A single typo, a single forgotten return tag, a single redirect added to a translated URL — any of those quietly disables the whole setup. Build it once with a generated source of truth, validate it with a crawler, and re-validate every release. SitemapFixer's scan flags missing return tags, invalid codes, and canonical conflicts across your whole sitemap in one pass.

Related Guides

Find every hreflang error on your site
Free analysis in 60 seconds
Analyze My Site Free
Related guides