Hreflang is an HTML attribute that tells search engines which language and regional version of a page to show to users in different locations. Without it, Google may show your English page to French users, or your German page to Austrian users looking for Swiss-German content. Near-identical regional pages can also be folded together as duplicates, so only one of them appears in results.
For businesses operating across multiple European markets, correct hreflang implementation is not optional. It decides which version of your pages appears in local search results, and that drives click-through rates, user experience and conversion. It is also easy to get wrong. When Ahrefs checked 374,756 domains that use hreflang in 2023, 67% had at least one hreflang issue, and a missing x-default alone showed up on 56.3% of them.
If you only take one workflow away from this guide, take this one. Run these eleven steps in order whenever you add a new locale or audit an existing set.
The order matters. Most broken implementations we audit fail at step 6 (a missing return link) or step 8 (a canonical quietly overriding the hreflang annotation), and both stay invisible until traffic to a secondary market starts sliding.
There are three ways to implement hreflang: HTML link elements in the page head, HTTP headers, and XML sitemaps. Google treats them as equivalent, so pick the one your stack can keep accurate.
HTML link elements are the most common method. You add a set of link tags in the head of every page, one for each language/region variant including a self-referencing tag. This is straightforward for small sites but becomes unwieldy when you have dozens of language variants.
HTTP headers are used for non-HTML resources (PDFs, for example) and can be useful when you cannot modify the HTML head. They carry the same information as the HTML method, in the server response headers.
XML sitemaps are the best approach for large sites with many language variants. You specify hreflang annotations in your sitemap using the xhtml:link element within each URL entry. This keeps the hreflang logic centralized, makes it easier to audit, and does not add weight to your HTML pages. For sites with 5+ language variants, we strongly recommend the sitemap approach.
Here is how the three methods compare at a glance:
| Method | Where it lives | Best for | Watch out for |
|---|---|---|---|
| HTML link elements | In the head of every page | Small sites with fewer than five locale variants | Every page needs the full tag set, so it gets unwieldy once you have many variants |
| HTTP headers | In the server response headers | Non-HTML files like PDFs, or pages where you cannot edit the head | Configured on the server rather than the page, which makes it harder to audit |
| XML sitemap | In the xhtml:link element inside each sitemap URL entry | Large sites with five or more language variants | Needs a sitemap generator that knows which pages are translations of each other |
Each example below is complete. Swap in your own URLs and codes, and keep the structure.
A shop with separate pages for the UK, the US, Germany and Austria, one French page for every French speaker, and a country selector at the root:
<!-- The same six lines go on all six URLs, the selector page included -->
<link rel="alternate" hreflang="en-GB" href="https://example.com/uk/" />
<link rel="alternate" hreflang="en-US" href="https://example.com/us/" />
<link rel="alternate" hreflang="de-DE" href="https://example.com/de/" />
<link rel="alternate" hreflang="de-AT" href="https://example.com/at/" />
<link rel="alternate" hreflang="fr" href="https://example.com/fr/" />
<link rel="alternate" hreflang="x-default" href="https://example.com/" />
The French page uses the bare fr code because it serves every French speaker. The four regional pages use language plus region because their prices and shipping differ by country. The selector at the root is the x-default.
The same three-locale product as the worked example below, declared in the sitemap instead of the head. Note the xmlns:xhtml namespace on the root element:
<?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/products/widget/</loc>
<xhtml:link rel="alternate" hreflang="en" href="https://example.com/products/widget/" />
<xhtml:link rel="alternate" hreflang="it" href="https://example.com/it/products/widget/" />
<xhtml:link rel="alternate" hreflang="es" href="https://example.com/es/products/widget/" />
<xhtml:link rel="alternate" hreflang="x-default" href="https://example.com/products/widget/" />
</url>
<url>
<loc>https://example.com/it/products/widget/</loc>
<xhtml:link rel="alternate" hreflang="en" href="https://example.com/products/widget/" />
<xhtml:link rel="alternate" hreflang="it" href="https://example.com/it/products/widget/" />
<xhtml:link rel="alternate" hreflang="es" href="https://example.com/es/products/widget/" />
<xhtml:link rel="alternate" hreflang="x-default" href="https://example.com/products/widget/" />
</url>
<url>
<loc>https://example.com/es/products/widget/</loc>
<xhtml:link rel="alternate" hreflang="en" href="https://example.com/products/widget/" />
<xhtml:link rel="alternate" hreflang="it" href="https://example.com/it/products/widget/" />
<xhtml:link rel="alternate" hreflang="es" href="https://example.com/es/products/widget/" />
<xhtml:link rel="alternate" hreflang="x-default" href="https://example.com/products/widget/" />
</url>
</urlset>
Every <url> entry lists every variant, itself included. Submit the file in Search Console under Sitemaps. If you move from head tags to the sitemap, remove the head tags in the same release, so the two sources cannot drift apart.
A PDF has no head, so its alternates travel in the response. The server sends the same Link header for all three files (shown on one line, as it is sent):
HTTP/1.1 200 OK
Content-Type: application/pdf
Link: <https://example.com/docs/guide.pdf>; rel="alternate"; hreflang="en", <https://example.com/it/docs/guida.pdf>; rel="alternate"; hreflang="it", <https://example.com/es/docs/guia.pdf>; rel="alternate"; hreflang="es"
Check what the server really sends with:
curl -sI https://example.com/it/docs/guida.pdf | grep -i '^link:'
Theory is easier to absorb with a concrete case. Suppose you run an e-commerce site with product pages in English (international), Italian, and Spanish. Your product URL structure looks like this:
https://example.com/products/widget/ (en, x-default)https://example.com/it/products/widget/ (it)https://example.com/es/products/widget/ (es)Each of those three URLs must carry a complete, reciprocal set of hreflang annotations. Here is what the head of the English page needs to contain:
<link rel="alternate" hreflang="en"
href="https://example.com/products/widget/" />
<link rel="alternate" hreflang="it"
href="https://example.com/it/products/widget/" />
<link rel="alternate" hreflang="es"
href="https://example.com/es/products/widget/" />
<link rel="alternate" hreflang="x-default"
href="https://example.com/products/widget/" />
The Italian page (/it/products/widget/) must contain the exact same four tags, with the same four URLs, unchanged. So does the Spanish page. All three pages carry the full set; the only thing that differs between them is their own canonical tag, which each page points to itself. If the Italian page drops its tag for the Spanish page, Google ignores the hreflang between those two pages. The other pairs keep working, and no Search Console report tells you the pair broke.
A few things worth noting in this example. The x-default and the English page share the same URL here, a common pattern for sites without a dedicated language-selector page. If you do have a selector page at /products/, point x-default there instead. Also note that the language codes are bare ISO 639-1 values (en, it, es) rather than region-qualified codes. Add a region qualifier (en-GB, es-MX) only when you serve different content to users in different countries speaking the same language; otherwise the bare code covers every speaker of that language.
The two tags do different jobs. A canonical picks one URL to index out of a group of duplicates. Hreflang links pages that are equivalent in different languages or regions. They work together only when each locale page is its own canonical.
Wrong. The Italian page names the English page as its canonical, so Google treats it as a duplicate of the English page and the hreflang on it has no effect:
<!-- On https://example.com/it/products/widget/ -->
<link rel="canonical" href="https://example.com/products/widget/" />
<link rel="alternate" hreflang="en" href="https://example.com/products/widget/" />
<link rel="alternate" hreflang="it" href="https://example.com/it/products/widget/" />
<link rel="alternate" hreflang="es" href="https://example.com/es/products/widget/" />
Right. The Italian page is its own canonical and lists every variant:
<!-- On https://example.com/it/products/widget/ -->
<link rel="canonical" href="https://example.com/it/products/widget/" />
<link rel="alternate" hreflang="en" href="https://example.com/products/widget/" />
<link rel="alternate" hreflang="it" href="https://example.com/it/products/widget/" />
<link rel="alternate" hreflang="es" href="https://example.com/es/products/widget/" />
<link rel="alternate" hreflang="x-default" href="https://example.com/products/widget/" />
To confirm the result, inspect the Italian URL in Search Console. The Google-selected canonical should be the Italian URL itself. Same-language regional pages that are near copies, such as en-GB and en-US with identical text, can still be folded into one canonical, so give them real local differences: price, currency, shipping and contact details.
Audit tools such as Ahrefs Site Audit, Semrush and Screaming Frog word hreflang problems slightly differently. The first column uses the wording you are most likely to see. Where the Ahrefs 2023 study of 374,756 domains measured an issue, the share of domains affected is in the second column.
| Error as audit tools name it | What it means | How to fix it |
|---|---|---|
| Missing return links (no return tags) | Page A points to page B, but B does not point back. Ahrefs found this on 15.3% of domains. | Build the tag set for every page from one locale map, so both pages print the same list. |
| Missing self-referencing hreflang | The page lists its siblings but not itself. Google says each language version must list itself. Ahrefs found the gap on 18% of domains. | Add the page's own URL with its own code. It costs one line and removes the warning. |
| x-default hreflang annotation missing | No fallback for users whose language matches none of your variants. The most common issue in the Ahrefs data, on 56.3% of domains. | Add x-default pointing to a language selector or your fallback version, with the same value on every page. |
| Hreflang to non-canonical | A URL in the set has a canonical that points somewhere else, often a parameter or trailing-slash variant. Found on 8% of domains. | List only canonical URLs. Fix the canonical or the hreflang so they name the same URL. |
| Hreflang to redirect or broken page (non-200 hreflang URLs) | A tag points to a 3xx, 4xx or 5xx URL, so Google cannot read a return link there. Found on 16.9% of domains. | Point each tag at the final 200 URL, and update hreflang in the same release as any redirect. |
| Invalid language or region code | Values such as eng (three letters), ua for Ukrainian, la for Latin America, or a country code with no language. Found on 4.6% of domains. | Use an ISO 639-1 language code, then an optional ISO 3166-1 Alpha 2 region: en-GB, pt-BR, de-AT. Google tolerates en-UK, but en-GB is the ISO value. |
| Hreflang and HTML lang mismatch | The page says lang="it" in its html tag but declares itself as en. Found on 3.2% of domains. Google ignores html lang for language detection, but the mismatch usually means a template prints the wrong locale. | Set both values from the same locale variable in the template. |
| Hreflang conflicts within page source code | The page gives two different URLs for one code, or two methods give different values. | Keep one entry per code per page. If you use two methods, make their values identical, or drop one. |
| Relative hreflang URLs | The href is /it/ instead of a full URL. Google asks for fully qualified URLs. | Write https://example.com/it/ in full. |
| Hreflang tags outside the head | The link elements sit in the body, or an invalid element earlier in the head ends the head before Google reaches them. | Print the tags high in the head, before scripts or widgets that inject body elements. |
| Hreflang to a page missing in that locale | The tag points at a locale version that does not exist, which usually returns a 404 or redirects to the homepage. | List only the variants that exist. Google lets you omit some languages on some pages. |
Hreflang annotations must be bidirectional: if Page A declares Page B as its French variant, Page B must also declare Page A as its English variant. If either direction is missing, Google ignores both annotations. Ahrefs found missing return links on 15.3% of the domains it checked.
This sounds simple but becomes complex at scale. If you have 10 language variants, every page needs 10 hreflang tags (including self-referencing), and all 10 pages must have matching annotations. That is 100 annotations that must be perfectly synchronized across 10 separate pages. One missing annotation breaks the link between that pair of pages, and a template bug breaks it on every page at once.
The solution is to generate hreflang annotations programmatically from a single source of truth, typically a database or CMS that knows which pages are translations of each other. Never manually maintain hreflang tags across localized pages.
Mistake 2: Wrong language and region codes. Hreflang uses ISO 639-1 language codes and optionally ISO 3166-1 Alpha 2 region codes. Common errors include "ua" for Ukrainian (ua is the country code; the language code is "uk"), "la" for Latin America (LA is the country code for Laos), and three-letter language codes, which Google does not support. British English is "en-GB", because GB is the ISO region code. Google also accepts "en-UK", since UK is a reserved code, but en-GB is the safe choice. Script codes are valid: "zh-Hans" and "zh-Hant" mark simplified and traditional Chinese, while "zh-CN" marks Chinese for users in China. Pick the one that matches how you split the content.
For European implementations, pay attention to regional variants: "pt" (Portuguese) is different from "pt-BR" (Brazilian Portuguese). "en-GB" and "en-US" are distinct variants. If you serve different content to Austrian ("de-AT") and German ("de-DE") users, you need separate hreflang annotations for each.
Mistake 3: Missing x-default tag. The x-default hreflang value tells search engines which page to show when no other hreflang variant matches the user's language or region. Without it, Google makes its own choice, which may not be what you want. Always include an x-default that points to your language selector page or your English-language version as a fallback.
Mistake 4: Canonical and hreflang conflicts. If a page has a canonical tag pointing to a different URL than what the hreflang annotation specifies, Google will follow the canonical and ignore the hreflang. Every page referenced in your hreflang annotations must have a self-referencing canonical tag. If your French page at /fr/product/ has a canonical pointing to /en/product/, your hreflang annotation for the French page will be silently ignored.
Mistake 5: Hreflang pointing to non-200 pages. Every URL in your hreflang annotations must return a 200 HTTP status code. If a URL returns a 301 redirect, a 404, or a 500 error, Google cannot read a return link from it, so the annotations that point to it are ignored. This commonly happens during site migrations or URL structure changes when old URLs are redirected but hreflang annotations are not updated at the same time.
To catch these issues, implement automated monitoring. Search Console has had no hreflang error report since Google removed the International Targeting report in September 2022. Crawlers such as Screaming Frog, Sitebulb, or Ahrefs Site Audit check every annotation on each crawl, so run one after every release that touches URLs or templates.
Getting the tags onto the page is only half the job. You need to confirm that Google reads them and that nothing breaks later. Google removed the Search Console International Targeting report on 22 September 2022, and no hreflang report has replaced it. Validation now combines a crawler, a raw source check, and the Search Console reports that still exist.
A dedicated hreflang crawler. Tools such as Screaming Frog (Configuration > Spider > Crawl linked XML sitemaps and hreflang), Sitebulb, and JetOctopus can follow hreflang relationships across your entire locale set in one pass. They surface broken reciprocal links, mismatched URLs between paired pages, and tags pointing to redirects or 404s, all in a single export. For any site with more than two or three locale variants, a crawl is the only realistic way to catch systematic errors.
A quick curl check for a specific URL. When you want to verify a single page immediately after deployment, you can inspect the raw HTML head without a full crawl:
curl -s "https://example.com/products/widget/" | grep -i hreflang
That prints every hreflang link element in the page source. Check that the count matches the number of locale variants you expect (including the x-default), and confirm each URL is absolute rather than relative. Google asks for fully qualified URLs, and audit tools flag relative ones.
Search Console, step by step. These reports do not list hreflang errors, but they show whether Google kept each locale page and served it in the right market:
How do I implement hreflang tags correctly?
Declare hreflang in one place per page (head link elements, HTTP headers, or XML sitemap entries) and keep that method consistent. Every variant references every other variant, including itself, and each reference has to be reciprocal. Ahrefs found missing self-references on 18% of domains and missing return links on 15.3%.
Where should hreflang tags go: HTML head, HTTP header, or sitemap?
HTML head suits sites with fewer than five locale variants. HTTP headers fit non-HTML files like PDFs or cases where you cannot edit the head. The sitemap method wins at scale, where you have hundreds of pages per locale and want to keep page weight down. Google treats the three methods as equivalent, and using more than one adds nothing. If you do use two, their values must match, because conflicting values are a common audit error.
What does x-default mean?
x-default is the fallback hreflang value. It tells search engines which page to serve when no language or region matches the user, and it usually points at a language selector or the primary English version. Leave it out and Google guesses, which often means the wrong locale shows up in unmapped regions.
Why is my hreflang not working?
Five usual suspects: a non-reciprocal link, a wrong ISO code (ua instead of uk for Ukrainian), a canonical that points to another locale, conflicting values from two declaration methods, or hreflang pointing at a URL that 404s or redirects. Search Console has had no hreflang report since 2022, so run a crawler like Screaming Frog and check the Google-selected canonical in URL Inspection before you conclude that Google is ignoring the tags.
What is the difference between hreflang and canonical tags?
A canonical tells Google which URL to index out of a group of duplicates. Hreflang tells Google which equivalent page to show to which language or region. Use both: each locale page is canonical to itself and lists every sibling in hreflang. A canonical that points to another locale cancels the hreflang for that page.
What if a page exists in some locales but not others?
List only the variants that exist. Google allows a different set on each page, so a page in English and Italian only carries en, it and x-default, while a page in all three locales carries all four values. Do not fill the gap by pointing the missing locale at the homepage or at a 404.
How do I keep hreflang right when the CMS generates localized URLs dynamically?
Generate the tags from the same locale map that builds the URLs, either at render time or in the sitemap job. When you launch a new locale, the map gets one new entry and every page in the cluster prints the new tag on its next render or sitemap build. Crawl the site after each launch to catch pages the map missed.
We have eight markets and no hreflang strategy. Where do we start?
Decide, market by market, whether you need one URL per language or one per country. Split by country only where prices, shipping, legal terms or the offer differ. Then build the locale map, pick one method (the sitemap scales best for large catalogues), ship it on one template, crawl it, and roll it out to the rest. The eleven-step checklist above covers the details.
Does hreflang help with duplicate content across country sites?
Yes, but only in a specific way. Hreflang does not suppress duplicate content signals the way a canonical does. What it does is tell Google that two similar pages are intended for different audiences, so they should be treated as separate, targeted results rather than competing copies. If your content is substantially identical across locales and the only differences are currency or date formats, you still need the annotations, but you should also be honest with yourself about whether those pages serve different user needs. Thin differentiation with hreflang is better than nothing, and real localization sends stronger signals.
How many hreflang tags can one page have?
Google documents no hard limit. Every variant adds one link element to every page in the cluster, so the head grows with each locale you launch. From five locale variants up, move to the sitemap method, which adds no weight to the page.
Do I need hreflang if my pages are in the same language but target different countries?
Yes. If you serve different content to, say, British and Australian English speakers (different pricing, product availability or legal terms), hreflang with region codes (en-GB and en-AU) tells Google to show each variant to the right audience. Without it, Google picks whichever version it considers most authoritative and can show it everywhere. Pages that differ only in the spelling of "colour" probably do not warrant the overhead; pages with different prices, shipping options or legal disclaimers do.
Part of our complete guide: International SEO →
This article is part of our comprehensive knowledge hub on international seo. Read the full guide for a complete strategic framework.
Our team helps companies implement the frameworks and strategies covered in this article.
Get in Touch