Short answer: hreflang is a tag that tells Google which language and country each version of a page is for, so a searcher in Germany gets your German page and a searcher in the UK gets your UK page. Every version must list all the others and itself, use valid codes such as de-DE or en-GB, and point only to live, indexable URLs. Hreflang does not fix duplicate content; it helps Google pick the right version for each market.
Below: the code for the three ways to add hreflang, the errors that quietly break it, how Shopify Markets, Webflow, WordPress and custom JavaScript platforms handle it, and two real fixes: a Shopify Plus store and a React shop where JavaScript doubled every tag.
Each language or country version of a page is an "alternate". On every alternate you list the full set: the other versions plus the page itself. Google only trusts a pair when both pages confirm each other (a "return link"). If page A points to page B but B does not point back, Google ignores that pair.
The code has two parts: a language in ISO 639-1 format (de, en, nl) and, optionally, a country in ISO 3166-1 alpha-2 format (DE, GB, BE). You can target a language alone (de), or a language in a country (de-AT), but never a country alone. Add x-default for the page people should get when none of your versions match, usually a language picker or your main international version.
Pick one method per site and use it everywhere. Mixing methods is allowed, but if the head tags and the sitemap disagree, you create the errors you were trying to avoid.
The most common method. This is the head of the German product page:
<!-- on https://example.com/de-de/schuhe/ -->
<link rel="canonical" href="https://example.com/de-de/schuhe/" />
<link rel="alternate" hreflang="de-DE" href="https://example.com/de-de/schuhe/" />
<link rel="alternate" hreflang="de-AT" href="https://example.com/de-at/schuhe/" />
<link rel="alternate" hreflang="en-GB" href="https://example.com/en-gb/shoes/" />
<link rel="alternate" hreflang="nl-NL" href="https://example.com/nl-nl/schoenen/" />
<link rel="alternate" hreflang="x-default" href="https://example.com/shoes/" />The canonical points to the page itself, and the German page lists itself as de-DE. The UK, Austrian and Dutch pages carry the same five alternate lines, each with its own self-referencing canonical.
Better for large sites, because it keeps the page head small and puts every set in one file you can check:
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:xhtml="http://www.w3.org/1999/xhtml">
<url>
<loc>https://example.com/de-de/schuhe/</loc>
<xhtml:link rel="alternate" hreflang="de-DE" href="https://example.com/de-de/schuhe/" />
<xhtml:link rel="alternate" hreflang="en-GB" href="https://example.com/en-gb/shoes/" />
<xhtml:link rel="alternate" hreflang="x-default" href="https://example.com/shoes/" />
</url>
<!-- repeat a <url> block for /en-gb/shoes/ and /shoes/ with the same three lines -->
</urlset>For files without an HTML head, such as PDFs:
Link: <https://example.com/de-de/preisliste.pdf>; rel="alternate"; hreflang="de-DE",
<https://example.com/en-gb/price-list.pdf>; rel="alternate"; hreflang="en-GB"Hreflang rarely fails loudly. Google just ignores the broken pairs and keeps showing the wrong version. These are the problems I find most often in audits:
| Error | What happens | Fix |
|---|---|---|
Invalid code, e.g. en-UK | The tag is ignored. UK is not a country code; uk as a language means Ukrainian | Use en-GB |
Country without language, e.g. be meant as Belgium | Read as the language Belarusian, or ignored | Use nl-BE or fr-BE |
| No return link | Google ignores the pair | Every alternate lists every other alternate |
| No self-reference | The set is incomplete | Each page lists itself too |
| Canonical points to another language version | The local page drops out of the index; hreflang is ignored | Canonical to the page itself |
| Alternates that redirect, 404 or are noindex | Those pairs are dropped | Only list live, indexable, canonical URLs |
| Relative URLs | Unreliable | Always use full URLs with https:// |
| Two sources of tags, e.g. theme plus translation app | Duplicate or conflicting sets | Let one system output hreflang |
| JavaScript adds a second set after the page loads | Two canonicals and a double hreflang set in the rendered page, while the server HTML looks fine | Let the script replace the server tags, and test the rendered page |
| Translated page, untranslated meta title | Right page, wrong-language snippet, low CTR | Translate SEO titles and descriptions per locale |
Shopify adds hreflang tags automatically for the languages and markets you publish, as subfolders (/de-de/) or separate domains. Most problems come from what sits on top: a translation app that adds a second set of tags, a theme with hard-coded alternate links, or markets where the language is not published. Check the rendered head of a few pages per market and make sure only one set appears. Also translate the SEO title and meta description in Translate & Adapt (or your translation app); Shopify does not do that for you.
Webflow Localization adds hreflang tags automatically for every published locale, so on localized sites the job is checking, not coding: confirm each locale is published and do not add manual tags on top. Sites without Localization that run separate language pages need the link tags in each page's custom code head, or built from CMS fields on collection templates.
WordPress has no built-in multilingual setup, so hreflang comes from the plugin that manages translations, such as WPML, Polylang or TranslatePress. Make sure your SEO plugin is not adding its own set as well, and recheck after plugin updates.
On a custom platform you build hreflang yourself, and the risk is not the server but the browser. Many sites render the head on the server and then let a JavaScript component manage the tags after the page loads. If that component only removes the tags it created itself, the server's tags stay and a second set is added next to them.
Example from a 2026 audit. A Dutch food-delivery shop selling in five languages moved from Shopify to its own React platform. A plain request (curl) showed a correct head. In the browser, after JavaScript, the same pages had two canonicals and 12 hreflang tags instead of one and six. The same component also replaced the page title with the bare brand name on 1,110 of the 1,165 URLs in the sitemap. The developer changed the component so it replaces the server tags instead of adding to them, live on 13 September 2026. A crawl with JavaScript rendering on 2 October 2026 found one canonical and six hreflang tags (nl, en, tr, de, fr, x-default) on 1,224 of 1,228 indexable pages, every page referring to itself, and no missing return links or alternates pointing to non-200 URLs. The other four pages only exist in Dutch and correctly carry nl and x-default.
The lesson: I first blamed server-side rendering, because the server response and the browser tab disagreed. Check both. A source-code check alone misses tags that JavaScript adds; a browser check alone misses what crawlers that do not run JavaScript receive.
Choosing between subfolders, subdomains and country domains is a separate decision; I compare them in international SEO: subdirectories vs hreflang.
Search Console no longer reports hreflang errors: Google removed the International Targeting report in 2022. Test it yourself instead:
curl:document.querySelectorAll('link[rel="canonical"]').length // should be 1
document.querySelectorAll('link[rel="alternate"][hreflang]').length // one per versionHreflang is one part of a wider check; the technical SEO audit guide covers the rest.
The problem. Muchachomalo, a Dutch underwear brand, sells across Europe on Shopify Plus. German searchers were seeing English meta titles, and the wrong language versions ranked in several countries. The self-referencing hreflang tags were missing, so Google did not treat the regional versions as a complete set. As a result, click-through rate in the international markets was underperforming.
The fix. We moved to a strict subfolder structure per market, corrected the hreflang output in the Shopify theme so every page listed all alternates plus itself, set each local page's canonical to itself, and localized the meta titles and descriptions, starting with Germany:

The result. German searchers started seeing German titles and descriptions, and international organic traffic grew in the months after the fix. The exact figures from that period are not included here.
Does hreflang fix duplicate content? No. Pages that are nearly identical across countries, such as UK and Irish English pages, are not a penalty risk anyway. Hreflang tells Google which of them to show where; the canonical decides which URL gets indexed.
Do I need x-default? It is not required, but recommended whenever you have a fallback: a language picker, or one version meant for everyone outside your target markets.
Is it en-GB or en-UK? en-GB. Country codes follow ISO 3166-1, where the United Kingdom is GB.
Can hreflang point to another domain? Yes. Alternates can sit on country domains such as example.de and example.nl, as long as every domain returns the links.
Does hreflang work on JavaScript sites? Yes, but put the tags in the server HTML and make sure scripts do not add a second set after loading. Test the rendered page, not only the source.
Should I use the head or the sitemap? The head is easier to check page by page; the sitemap scales better for large catalogues. Use one, consistently.
Working on an international Shopify or Webflow store? Email me for an hreflang check. For how this fits AI search, read how GEO and SEO fit together in 2026.