Snoika
Technical SEO·How-to guide

Hreflang: how to implement it without the errors that break it

Hreflang is an HTML attribute that tells Google which language or regional version of a page to show each searcher. You add it as a set of link annotations, and every version of the page lists itself and all its alternates. On a small site, setting it up takes an afternoon. The tags break when a page leaves out a return link or uses a code Google doesn't recognise.

Portrait of Anton VedeshinAnton VedeshinFounder, Snoika · 8 min read
Two people in their forties in a glass-walled meeting room with a whiteboard.

When your site needs hreflang

Your site needs hreflang when the same content exists at more than one URL, in different languages or for different regions. A shop with English pages for the US and the UK fits that rule, even though the text barely changes between the two. So does a software company that translates its pricing page into German and French. In each case, hreflang tells Google to show the right URL in the right market, so a searcher in Vienna doesn't land on the US price list.

Hreflang only maps pages to each other. Google's algorithms identify a page's language from its content, according to its documentation on why it doesn't use hreflang for that task. A site in one language for one market gets nothing from the tags, and adding them anyway gives you more code to maintain.

John Mueller, Search Advocate at Google, described hreflang on X as "one of the most complex aspects of SEO" because it "gets really hard quickly." Before you write a single tag, list every page that has a localized twin and the URL of each version. That list is the input for every step below.

Choosing your implementation method

Pick one of the three places Google reads hreflang from and use only that one. Google accepts HTML link elements in the page head or annotations in HTTP response headers. It also accepts entries in the XML sitemap. All three carry the same information. They differ in who maintains them and in which files they can cover.

HTML `<link>` elements

Where it lives
The `<head>` of each HTML page
Fits best
Small and mid-sized sites where the CMS templates the head
Main drawback
Every page carries every alternate, so the markup grows with each new language

HTTP headers

Where it lives
The server response for each URL
Fits best
PDFs and other files with no HTML head
Main drawback
Hard to audit, because the tags don't show in the page source

XML sitemap

Where it lives
`<xhtml:link>` entries inside each `<url>`
Fits best
Large sites with many languages
Main drawback
Changes only reach Google when it next fetches the sitemap

Most sites end up with the HTML head because the CMS or a plugin already writes there. Sites with dozens of locales move to the sitemap because the page code stays light. Don't use two methods at once. In its hreflang study, Ahrefs found that conflicting annotations come from a mismatch between page code and sitemaps, and Google then has to guess which set to trust.

Before you move on, check that the method you chose can reach every URL on the list you built in the previous section.

Writing correct hreflang tags

To add hreflang tags to your site, give every page in a language set a tag for itself and for each alternate version. The steps below use the HTML method. The same logic applies to headers and sitemaps.

  1. Take one cluster from your URL list, for example the English pricing page with its German and French versions.
  2. Pick the code for each version: a language code, plus a region code when the page targets one country.
  3. Write one `<link rel="alternate" hreflang="..." href="..." />` line per version, with the full URL.
  4. Add an `x-default` line for searchers who match none of your codes.
  5. Paste the same block of lines into the `<head>` of every page in the cluster.
  6. Repeat for the next cluster, then crawl the site to confirm each page shows the full block.

For a three-language pricing page, every one of the three pages carries this block:

<link rel="alternate" hreflang="en" href="https://example.com/pricing/" />
<link rel="alternate" hreflang="de" href="https://example.com/de/preise/" />
<link rel="alternate" hreflang="fr" href="https://example.com/fr/tarifs/" />
<link rel="alternate" hreflang="x-default" href="https://example.com/pricing/" />

Which language and region codes are valid

The language code comes first and uses the ISO 639-1 format, such as `de` or `fr`. The optional region follows a hyphen in ISO 3166-1 Alpha 2 format, such as `de-AT`. A region on its own is invalid, so `hreflang="at"` means nothing to Google. The UK is the classic trap: its region code is `GB`, so English for Britain is `en-GB` and `en-UK` is an error. Case doesn't matter, but keep it consistent so your audits stay readable.

Which URLs belong in the href

Every `href` needs a fully qualified URL, including the protocol and the domain. Google's syntax notes specify the fully-qualified URL of each alternate. A relative path like `/de/preise/` doesn't qualify. Each URL must also return a 200 status code and be the canonical version of itself. If a German page canonicalizes to the English one, you're telling Google two opposite things about the same URL.

Setting x-default correctly

The x-default tag is optional for hreflang, but add one whenever there's a sensible page for searchers outside your listed markets. Google defines `x-default` as the value that matches any language not explicitly listed in your tags. Without it, a searcher in Portugal on a site with only English, German and French versions gets whichever page Google picks.

Point x-default at the page that serves an unmatched visitor best. That's a country or language selector, or your main English version. On a site that only publishes in German, point it at a page where non-German speakers can still convert. One page can be both a listed language and the x-default, as in the pricing example above.

Teams leave this tag out more than any other. Ahrefs' 2023 study of hreflang sites found x-default missing on 56.3% of them. Check that the x-default line is identical in every page of a cluster, because it follows the same return rules as any other tag.

Return links are mandatory

Every page you list in a hreflang set has to list the page that listed it. If the English page points to the German page and the German page doesn't point back, Google ignores the pair. Google requires that rule because anyone could otherwise claim another site's page as their own translation. A return link proves that both page owners agree.

Ahrefs' study found that missing return links affected 15.3% of domains; 18% lacked the self-referencing tag. When a page leaves itself out, its cluster is incomplete. Mueller has said the self-reference is optional, but the major audit tools flag it, and including it costs one line.

To fix hreflang return tag errors, start from the audit report. It names the page that points out and the page that fails to point back. Open the second page and add the missing line. Then check why it was missing. The usual causes are:

  • A translated page was published after the others and never got the full block.
  • One locale runs on a separate CMS or subdomain with its own templates.
  • The return link points to a redirect or non-canonical URL instead of the exact listed one.
  • A page was deleted or noindexed, but the other pages still list it.

The lasting fix is to generate every block from one list of URL clusters, so adding a locale updates all pages at once. Hand-edited tags will drift again within a few releases.

Examples for EU markets

European sites need region codes more than most, because the same language covers several countries with different prices and laws. Spelling also differs between these countries. German is spoken in Germany and Austria. It is also spoken in Switzerland. A site with separate pages for those three markets carries this block on each of them:

<link rel="alternate" hreflang="de-DE" href="https://example.com/de-de/" />
<link rel="alternate" hreflang="de-AT" href="https://example.com/de-at/" />
<link rel="alternate" hreflang="de-CH" href="https://example.com/de-ch/" />
<link rel="alternate" hreflang="de" href="https://example.com/de-de/" />
<link rel="alternate" hreflang="x-default" href="https://example.com/" />

The plain `de` line catches German speakers in Luxembourg or Belgium, who match no regional code. Belgium needs extra care because its pages target two languages. Use `nl-BE` and `fr-BE` for the Belgian versions, and keep them separate from `nl-NL` and `fr-FR` if prices or delivery terms differ.

<link rel="alternate" hreflang="nl-BE" href="https://example.com/be-nl/" />
<link rel="alternate" hreflang="fr-BE" href="https://example.com/be-fr/" />
<link rel="alternate" hreflang="nl-NL" href="https://example.com/nl/" />
<link rel="alternate" hreflang="fr-FR" href="https://example.com/fr/" />

Only create a regional split when the pages actually differ. Two identical German pages for Germany and Austria, with the same euro prices, add maintenance without helping anyone. One `de` page would serve both.

Hreflang in XML sitemaps

In a sitemap, each `<url>` entry carries one `<xhtml:link>` line for every version of that page, itself included. You also declare the XHTML namespace in the opening `<urlset>` tag. Here's the English pricing page from earlier:

<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
        xmlns:xhtml="http://www.w3.org/1999/xhtml">
  <url>
    <loc>https://example.com/pricing/</loc>
    <xhtml:link rel="alternate" hreflang="en" href="https://example.com/pricing/" />
    <xhtml:link rel="alternate" hreflang="de" href="https://example.com/de/preise/" />
    <xhtml:link rel="alternate" hreflang="x-default" href="https://example.com/pricing/" />
  </url>
</urlset>

The German and French URLs each need their own `<url>` entry with the same set of links. That repetition is how the sitemap expresses return links. Leave one entry out and the cluster breaks, just as it would in HTML.

Once the sitemap holds your hreflang, remove the tags from the page head so the two sources can't drift apart. Submit the sitemap in Google Search Console. Then confirm Google has fetched it recently, because your changes don't count until it does.

Fixing common hreflang errors

Most hreflang errors trace back to a handful of causes, and each one has a fix you can make at the website template level. Ahrefs' study of 374,756 domains found 67% had at least one hreflang issue. The table lists errors audit tools report, with the share of domains Ahrefs measured where it reported one.

Missing self-reference

What it looks like
Page lists only its alternates
Fix
Add a line pointing to the page's own URL
Share of domains (Ahrefs)
18%

Missing return link

What it looks like
Page A lists page B, page B doesn't list page A
Fix
Add the line on page B, then generate all blocks from one list
Share of domains (Ahrefs)
15.3%

Same page for two languages

What it looks like
One URL appears under `en` and `de`
Fix
Give each language its own URL
Share of domains (Ahrefs)
2.5% of domains

Invalid code

What it looks like
`en-UK`, `eng`, or a region alone such as `at`
Fix
Use ISO 639-1 language plus ISO 3166-1 region, as in `en-GB`
Share of domains (Ahrefs)
Not reported

Non-canonical target

What it looks like
Tag points to a URL whose canonical is a different page
Fix
Point the tag at the canonical URL, or fix the canonical
Share of domains (Ahrefs)
Not reported

Broken or redirected target

What it looks like
Tag points to a 404 or a 301
Fix
Replace with the live final URL
Share of domains (Ahrefs)
Not reported

Fix the errors in order of reach. A template bug, such as a wrong code in the site-wide header, breaks every page at once, so it comes first. A single missing return link on a low-traffic page can wait for the next pass. After each fix, recrawl the affected cluster rather than the whole site so you can see the change take effect.

Validating your hreflang setup

Your hreflang setup works when a crawler finds no errors and Google shows the right version in each market. Run both checks, because the first can pass while the second fails.

Start with a site crawler that reads hreflang. Screaming Frog and Sitebulb report missing return links and invalid codes. Ahrefs Site Audit reports these errors too. Ahrefs' audit also checks the head and HTTP headers against sitemap annotations to catch conflicts between methods. Set the crawl to recur weekly or monthly, since new pages are where errors creep back in.

Next, open Google Search Console and use URL Inspection on one page per locale. Check that the Google-selected canonical matches the URL you listed in your tags. If Google picked a different canonical, it's treating your translation as a duplicate, and the hreflang for that page won't count.

Last, search for a branded query as a user in each target market would. You can change the country and language settings in Google's search preferences. Check that a searcher in Austria sees the `de-AT` URL. If they don't after Google has recrawled the cluster, go back to the error table.

What to do next

Build your URL cluster list first and make it the single source every hreflang block is generated from. Every fix on this page rests on it. Return links and x-default go wrong when someone edits one page by hand and forgets the others. The same mistake affects sitemap entries. With the list in place, adding a new locale becomes one entry instead of an edit on every page in the set.

Snoika audits websites for technical SEO errors like these and tracks how each language version appears in Google and in AI assistants, and its team reviews the results. To check your hreflang setup with a person who works on this every day, talk to an AI search expert.

FAQ

See how AI talks about your brand

Get free audit