How to Implement Hreflang (Step by Step)

Working code for all three hreflang methods — HTML head tags, HTTP Link headers, and XML sitemap annotations — plus the syntax rules, at-scale CMS automation, and the validation workflow to confirm it actually shipped correctly.

First published: Jul 2, 2026 · Last updated: Jul 26, 2026 · Advanced
demand #2 in Hreflang#5 in International SEO#125 on the site
1 evidence signal on this page

There are exactly three ways to add hreflang — HTML link tags in the head, HTTP Link response headers (for non-HTML files like PDFs), or xhtml:link entries in an XML sitemap (best at scale). Pick one; Google treats all three as equivalent and there's no benefit to combining them. Every method obeys the same two non-negotiable rules: self-reference (each page lists itself) and reciprocity (if A points to B, B must point back to A, or Google ignores the pair). Codes are ISO 639-1 language plus optional ISO 3166-1 alpha-2 region; x-default is the fallback. Generate it all from a single source of truth, then validate the rendered head, not just view-source — in my study of 374,756 domains, over 67% of hreflang setups had at least one issue.

TL;DR — Three methods, pick one: HTML <link> tags in the <head>, HTTP Link: headers (the only option for non-HTML files like PDFs), or <xhtml:link> entries in an XML sitemapAn XML sitemap is a UTF-8 file listing the canonical URLs on your site (with optional lastmod) so search engines can discover and prioritize them. It's a discovery and diagnostic aid, not a guarantee of indexing — and Google ignores its priority and changefreq tags. (best at scale — one machine-generated file, easiest to QA). Google says the three are equivalent and there’s no benefit to combining them. Every method obeys self-reference + reciprocity, uses absolute URLs, and uses ISO 639-1Two-letter language codes used in hreflang (en, es, de). language + optional ISO 3166-1 alpha-2Two-letter country codes used in hreflang region targeting (us, gb, au). region codes. Generate the whole thing from one source of truth — hand-maintained hreflangHreflang is an annotation (in HTML, HTTP headers, or XML sitemaps) that tells search engines which language and optional region a page targets, and which alternate versions exist. It only works when every page in the cluster references all the others. rots. Then validate the rendered head, crawl the whole cluster for reciprocity, and keep re-checking after every URL change. Do not auto-redirectA redirect sends browsers and crawlers from a requested URL to a different one. An HTTP redirect specifically is a 3xx status code paired with a Location header; meta refresh and JavaScript redirects achieve a similar navigation without being a 3xx response themselves. Permanent redirects (301/308) are Google's signal the target should be canonical; temporary ones (302/303/307) aren't. crawlersA crawler — also called a spider or bot — is an automated program that fetches web pages, extracts their links, and queues new URLs to visit. Search engines use crawlers to discover and download content for their index. by geoGenerative Engine Optimization (GEO) is the practice of optimizing content and brand presence so AI-powered search engines and assistants — Google AI Overviews, ChatGPT, Perplexity — cite, recommend, or mention you when generating answers. Google's position is that it's still SEO..

Before you write a line of code: three decisions

1. Which method. Google is explicit that the choice is about convenience, not performance — “The three methods are equivalent from Google’s perspective and you can choose the method that’s the most convenient for your site.” My rough rule:

Evidence for this claim Google supports equivalent HTML, HTTP-header, and sitemap methods for declaring localized versions; HTTP headers can be used for non-HTML files such as PDFs. Scope: Google Search hreflang implementation methods. Confidence: high · Verified: Google: Localized versions
  • A handful of URLs, a static or simple site, few locales → HTML <link> tags.
  • Non-HTML assets (PDFs, docs) → HTTP Link: headers (you have no other option — there’s no <head> in a PDF).
  • Many locales, an existing sitemapA sitemap is a file that lists the pages, images, videos, and other files on your site so search engines can discover them. It helps discovery, but submitting a sitemap doesn't guarantee crawling or indexing. pipeline, or a headless/JAMstack setup → XML sitemapAn XML sitemap is a UTF-8 file listing the canonical URLs on your site (with optional lastmod) so search engines can discover and prioritize them. It's a discovery and diagnostic aid, not a guarantee of indexing — and Google ignores its priority and changefreq tags., generated programmatically.

The Decision Trees lens walks this as an actual flowchart.

2. One source of truth. Whatever method you pick, the annotations should be generated from one place — a locale/translation table in your database, a CMSA content management system (CMS) is software that lets users create, manage, and publish digital content — like blog posts and pages — without writing raw code. WordPress, Drupal, and Joomla are the most common open-source CMS platforms. relationship field, or (for small sites) a single spreadsheet. The single worst hreflangHreflang is an annotation (in HTML, HTTP headers, or XML sitemaps) that tells search engines which language and optional region a page targets, and which alternate versions exist. It only works when every page in the cluster references all the others. anti-pattern is hand-maintaining tags per page. The moment one locale’s template drifts, return links go missing and pairs get dropped.

3. Do not combine methods. You can run all three; Google says there’s no benefit, and every extra method is another surface for the three copies to disagree with each other.

Evidence for this claim Google requires fully qualified alternate URLs and reciprocal links, and recommends including each page itself in its alternate set. Scope: Google Search hreflang rules shared by all delivery methods. Confidence: high · Verified: Google: Hreflang guidelines

The syntax, verbatim from Google, is:

<link rel="alternate" hreflang="lang_code" href="url_of_page" />

A full working set for a multi-language, multi-region cluster with a fallback:

<link rel="alternate" hreflang="en-us" href="https://example.com/us/" />
<link rel="alternate" hreflang="en-gb" href="https://example.com/uk/" />
<link rel="alternate" hreflang="de" href="https://example.com/de/" />
<link rel="alternate" hreflang="x-default" href="https://example.com/" />

That exact block goes on every page in the cluster (the US page, the UK page, the German page, and the homepage) — each one includes its own self-referencing line. That’s what satisfies reciprocity.

Placement is a hard rule. Google: “The <link> tags must be inside a well-formed <head> section of the HTML.” And their own troubleshooting advice: “If in doubt, paste code from your rendered page into an HTML validator to ensure that the links are inside the <head> element.”

This matters more than it sounds, because of a failure mode most written guides skip: hreflang tags can be forced out of the <head> and into the <body>, where they’re invalid. In my Pubcon Vegas 2019 deck I flagged this directly — tags can’t legitimately live in the body (that would let one site hijack another’s alternates), but a malformed or injected <p> tag, or an iframe, can prematurely close the <head> so that everything after it — including your hreflang — renders inside the <body>. The tag is technically present in source but silently invalid. The fastest way to debug it is browser DOM breakpoints: watch where the tags actually land in the rendered DOM, then walk back to the markup that broke the <head>.

When HTML tags make sense: small-to-medium sites with a manageable number of locales, where the per-page <link> block doesn’t balloon. On a site with dozens of locales, every page carries a large block of markup — which is one reason big sites lean on the sitemapA sitemap is a file that lists the pages, images, videos, and other files on your site so search engines can discover them. It helps discovery, but submitting a sitemap doesn't guarantee crawling or indexing. method.

Don’t mix hreflang with other alternate attributes on the same <link> element. Google’s guidance is explicit that a <link rel="alternate"> carrying hreflang shouldn’t also carry an unrelated alternate attribute like media in the same tag — if you need both a language/region alternate and a media-query alternate for the same URL, that’s two separate <link> elements, not one combined one. Mixing them is an easy way to end up with a tag Google can’t parse as either.

Same information, sent in the HTTP response instead of the HTML. The syntax:

Link: <https://example.com/file.pdf>; rel="alternate"; hreflang="en",
      <https://de-ch.example.com/file.pdf>; rel="alternate"; hreflang="de-ch"

Note the angle brackets around each URL, the semicolon-separated attributes, and the comma separating each alternate. Every URL in the header is comma-appended.

When you need this: non-HTML resources. A PDF, a .doc, an image served directly — none of them have a <head> to hold <link> tags, so the header is your only way to attach hreflang to them.

Configuration considerations: you set these headers at the server or CDN layer (an Apache Header directive, an Nginx add_header, a Cloudflare/Fastly/CloudFront response-header rule, or your application’s response). Because the header is set by infrastructure rather than per-document markup, the reciprocity rule is easy to break here — the English PDF’s header and the German PDF’s header are usually configured separately, so it’s on you to make sure each one lists the whole set, itself included. Keep the header generation driven by the same locale table as your HTML/sitemap approach.

The invariant to hold onto: every alternate response carries the identical complete set, every time. It’s not enough for the English PDF’s header to list the German alternate — the German PDF’s response has to carry the exact same set back (itself plus every other alternate), on every single response, not just the first one a crawlerA crawler — also called a spider or bot — is an automated program that fetches web pages, extracts their links, and queues new URLs to visit. Search engines use crawlers to discover and download content for their index. happens to hit. Treat the header as generated output, not a one-off you set and forget.

At scale, this is usually the right call: the whole cluster lives in one (or a few) machine-generated files, nothing bloats each page’s <head>, and — because the entire relationship graph is in one place — it’s by far the easiest method to QA. The syntax:

<?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://www.example.com/english/page.html</loc>
    <xhtml:link rel="alternate" hreflang="de"
                href="https://www.example.de/deutsch/page.html"/>
    <xhtml:link rel="alternate" hreflang="en"
                href="https://www.example.com/english/page.html"/>
  </url>
  <url>
    <loc>https://www.example.de/deutsch/page.html</loc>
    <xhtml:link rel="alternate" hreflang="de"
                href="https://www.example.de/deutsch/page.html"/>
    <xhtml:link rel="alternate" hreflang="en"
                href="https://www.example.com/english/page.html"/>
  </url>
</urlset>

Two things that trip people up in the sitemap method:

  1. The namespace declaration is mandatory. That xmlns:xhtml="http://www.w3.org/1999/xhtml" on the <urlset> element is not optional decoration — omit it and every <xhtml:link> in the file is invalid.
  2. Every <url> block must be self-complete. Notice that both <url> blocks above list both alternates, including their own <loc>. Each <url> entry carries the full set (self-reference + all alternates). Reciprocity in a sitemap is just “every URL’s block lists every URL in the cluster, itself included.”

Two behaviors worth knowing so you don’t over-engineer the generator: the order of the <xhtml:link> children inside a <url> block doesn’t matter to Google — don’t waste time sorting them — and those <xhtml:link> annotations don’t count against your sitemap’s 50,000-URL-per-file limit, since they’re children of a <url> entry, not separate <url> entries themselves.

Generating it programmatically. The whole point of the sitemap method is that it’s a byproduct of your CMS’s own translation data. If your CMS already knows that /english/page.html and /deutsch/page.html are translations of each other, your sitemap generator should walk that relationship table and emit the <xhtml:link> blocks at build time (or at sitemap-request time). Do that and hreflang can’t drift out of sync with reality — it’s regenerated from the source of truth every time, and adding a locale is a data change, not a hand-edit across hundreds of pages.

For teams without dev resources, I built a lightweight Google Sheets template in my Ahrefs hreflang guide: a Setup tab (pick a default language plus up to four variants), a URLs tab (paste each language’s URLs into columns), and a Results tab that auto-generates the sitemap XML block for you. It’s the “single source of truth” idea made concrete for sites too small to justify a real CMS integration.

The rules that apply no matter which method you pick

These are method-agnostic and non-negotiable.

  • Self-reference. Every page (or every <url> block, or every header) lists itself. Mueller frames this as optional-but-good-practice, but skipping it was present in 18.0% of the domains in my study — automate it and it costs nothing.
  • Reciprocity. “Each language version must list itself as well as all other language versions.” And the enforcement: “If two pages don’t both point to each other, the tags will be ignored. This is so that someone on another site can’t arbitrarily create a tag naming itself as an alternative version of one of your pages.” One missing return link drops the pair.
  • Absolute, fully-qualified URLs. “Alternate URLs must be fully-qualified, including the transport method (http/httpsHTTPS is the encrypted version of HTTP — it uses TLS to authenticate the server and protect data in transit between a browser and a website. Google announced it as a lightweight ranking signal in 2014 and today conditionally prefers HTTPS pages as canonical; Chrome marks plain HTTP pages 'Not Secure.')” — so https://example.com/foo, never //example.com/foo or /foo. And the URL must match the exact form Google indexes: protocol, www, trailing slashA trailing slash is the forward slash (/) at the end of a URL — example.com/page/ versus example.com/page. Except at the bare root domain, the two versions are different URLs to search engines, so you pick one format and enforce it., and case all have to line up, or the return-link match fails.

Getting the codes right

“The first code of the hreflang attribute is the language code (in ISO 639-1 format) followed by an optional second code that represents the region code (in ISO 3166-1 Alpha 2 format).” Combine them with a dash: en-US. You can target a language on its own (es = Spanish everywhere), but you cannot target a region on its own — there’s always a language first.

The mistakes I see most often (and saw across the study dataset):

  • en-UK instead of en-GBuk is Ukrainian, the UK’s region code is gb.
  • jp instead of ja for Japanese, cn instead of zh for Chinese.
  • Three-letter codes (ger, eng) where two-letter ISO 639-1 is required.
  • Using EU, UN, or UK as region codes — none are valid ISO 3166-1 alpha-2 targets.
TIP

Lint locale codes and generate a consistent cluster from one URL matrix with the Hreflang Generator + Linter Free

  1. Enter each language or language-region code with its absolute locale URL.
  2. Fix invalid codes, separators, duplicates, and URL-format errors before exporting tags.
  3. Deploy from the same source of truth, then crawl the live cluster separately to confirm self-reference and reciprocity.
Locale subtags use a hyphen: write `en-US`, not `en_US`. Validate the code format before generating tags across an entire locale cluster.

A focused Hreflang Generator error says en_US uses an underscore, explains that hreflang subtags are hyphen-separated, and recommends en-US.

x-default is the reserved value for the fallback — a language selector or an auto-redirecting home page that serves users who match none of your explicit locales:

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

It’s not mandatory. It was the single most-missing element in my study (56.3% of domains omitted it), but a missing x-default doesn’t break the cluster the way a missing reciprocal tag does — Google just falls back to its own language/region detection for unmatched users. Add it anyway; it’s a checklist item, not a cluster-breaker. (There’s a dedicated x-defaultx-default is the reserved hreflang value that points to a fallback URL — the page Google shows when a user's locale doesn't match any of your other hreflang tags. It is optional and does not mean \"English.\" subtopic in this cluster.)

Implementing at scale — CMS and platform approaches

WordPress. Yoast SEO does not generate hreflang on its own. To actually output tags you need a multilingual plugin — WPML or Polylang. WPML auto-generates hreflang for every page that has translations, adds an x-default pointing at your default-language version, and by default injects the annotations into the XML sitemap; there’s a setting under WPML → Languages → SEO Options (“Display alternative languages in the HEAD section”) if you want head-tag output instead of, or in addition to, the sitemap. (See WPML’s docs on using it with Yoast.)

Shopify. Shopify Markets automatically generates mutually-linked hreflang tags once you’ve configured markets/languages and the content is published and linked in navigation — which removes the most common failure mode (missing reciprocal links) by design. The catch: Markets’ automatic tags are often language-only (fr, de) rather than language-region (fr-FR, fr-CA), which under-serves brands that need regional specificity — French for France vs. Canada vs. Belgium. For that you drop to manual <link> tags in theme.liquid (Shopify’s own theme hreflang docs show the pattern) or an app — and be careful, because mixing automatic Markets tags with manual/app tags is a documented source of conflicts. Pick one.

Custom / headless / enterprise. Generate the annotations from your translation-relationship table at build or serve time, as described in the sitemap section above. This is the same “single source of truth” principle — hreflang becomes a computed output of data your CMS already holds, not a hand-maintained artifact that can rot.

Implementation mistakes that break the cluster

Auto-redirecting by geoGenerative Engine Optimization — visibility inside AI answer engines./IP. This is a separate failure from syntax errors and it’s worse. If you redirect visitors — and especially crawlers — to a version based on perceived location, you can de-index entire regional clusters, because GooglebotGooglebot is Google's web crawler — the software that fetches pages so Google can index and rank them. It comes in two variants, Googlebot Smartphone (primary, under mobile-first indexing) and Googlebot Desktop, and runs an evergreen Chromium renderer. mostly crawls from the US. As I put it in that Pubcon deck: a geo-redirectA geo-redirect automatically sends a visitor to a location- or language-specific URL based on IP or browser signals — a UX/serving mechanism that's separate from, and can actively undermine, hreflang. “would redirect search engines to where they crawl from. Google for instance mostly crawls from the US so we would effectively de-index all geo pages.” There’s a regulatory angle too — potential exposure under EU anti-geoblocking rules. The right pattern is redirect users, never crawlers: detect and (optionally) redirect human visitors, but always let bots reach every URL variant directly, with hreflang doing the actual routing signal. Google’s own multi-regional guidance recommends a non-intrusive suggestion banner over automatic redirection for exactly this reason.

Pointing hreflang at non-canonical, redirected, or noindexed URLs. If a locale’s URL changes and the redirect goes in but the hreflang still points at the old URL, your cluster now references a 301 or a 404 (16.9% of domains in my study referenced broken/redirected pages). Similarly, each variant should canonicalize to itself; pointing hreflang at a URL that’s canonicalized away, or at a noindexed page, breaks the return link (8.0% pointed at non-canonical URLs).

Inconsistent URL formats. The hreflang URL and the indexed URL have to be byte-identical in form — trailing slashA trailing slash is the forward slash (/) at the end of a URL — example.com/page/ versus example.com/page. Except at the bare root domain, the two versions are different URLs to search engines, so you pick one format and enforce it., www, protocol, and case all included. A mismatch here is a silent reciprocity failure.

Validating your implementation after launch

View rendered source, not just raw source. If your hreflang is injected by client-side JavaScript, curl or Ctrl+U (“View Page Source”) will show nothing — but the tags exist in the rendered DOM. Confirm with GSC URL Inspection → “Test Live URL” → “View Tested Page,” or a renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM.-capable crawler. This is the single most common reason someone says “my tags are missing” when they’re actually fine (or the reverse — present in source but broken because they render in the <body>).

Crawl the whole cluster, don’t spot-check one URL. Reciprocity is a relationship between pages, so checking one page tells you almost nothing. Run a crawler across the whole cluster — Screaming Frog’s hreflang audit or Ahrefs Site Audit — to catch missing return tags, non-canonical targets, and broken references at scale. Ahrefs Site Audit’s Hreflangs tab draws the cluster as a graph with broken links in red, which is far easier to reason about than a CSV.

Verify real SERPs with &hl= and &gl=. Append the host-language (&hl=) and geolocation (&gl=) parameters to a Google search URL to preview what a given locale’s results actually look like, rather than guessing from your own location.

Validation is not a one-time step. New locales, URL/redirect changes, and dev/staging config bleeding into production are the recurring causes of post-launch breakage. Build hreflang checks into regression tests and recurring crawls, not just a launch-day QA pass. My framing from the talks: “any number of things can break from masking to things carrying over from dev/test/staging environments.”

Myths worth retiring

  • “Sitemaps process faster than HTML tags.” False. Both are resolved at crawl time — I’ve debunked this directly. The sitemap’s advantage is maintainability and QA, not speed.
  • “Yandex doesn’t support hreflang in sitemaps.” False — Yandex’s own documentation confirms sitemap hreflang support.
  • “Use all three methods for extra signal.” No benefit per Google, and it just multiplies inconsistency risk.
  • “A missing x-default breaks the cluster.” False — it’s optional; Google falls back to its own detection.
  • “Wrong hreflang gets you penalized.” False — it’s a hint, not a directive. Broken hreflang is ignored, not penalized.

Where to go next

This article is the how-to depth under the hreflang hub — the hub covers what hreflang is, why reciprocity matters, and what Bing does instead. The x-defaultx-default is the reserved hreflang value that points to a fallback URL — the page Google shows when a user's locale doesn't match any of your other hreflang tags. It is optional and does not mean \"English.\" subtopic goes deep on the fallback value. For the strategy this implements, see the International SEOInternational SEO is the practice of optimizing a site so search engines understand which countries and/or languages it targets, and serve the right version to each user. It spans URL structure, hreflang, and on-page localization. pillar — hreflang is the technical layer, not a substitute for genuine localizationLocalization is adapting content for a specific target market — not just translating the words, but adjusting currency, formats, idioms, cultural references, local search terms, and trust signals so the experience feels native..

Add an expert note

Pin an expert quote

New person? Create their unclaimed profile at /admin/experts/ → Pin a quote first.