Discuss project

Hreflang: setup, common mistakes and how to check it

How to set up hreflang on a multilingual site, which mistakes break the whole markup at once, and how to verify that Google actually accepted it.

Vladislav KrivorutskoJuly 20, 20268 min read
Contents

TL;DR - key points

  • Hreflang does not improve rankings. Its job is to serve the right language version to the right user and keep versions from competing with each other
  • Three rules break almost every setup: links must be reciprocal, every page must reference itself, and every URL must be absolute and return 200
  • One mistake in one cluster usually disables hreflang for the entire cluster, not just for a single page
  • Where you put it (`<head>`, sitemap or HTTP header) makes no difference to the result — pick the one you can generate automatically
  • Google treats hreflang as a hint: even with correct markup it will sometimes show a different version

The short answer

Hreflang is markup that tells Google several pages are language versions of one another. It does not improve rankings: its job is to serve the right version to the right user and stop your versions from competing in the same result set. The setup comes down to three rules: links are reciprocal, every page references itself, and every URL is absolute and returns a 200 status.

Everything else in this article is about why hreflang still breaks even when those three rules are followed, and how to spot it.


What hreflang is actually for

"So Google understands the languages" is too vague to configure anything against. In practice hreflang solves two problems.

First, version selection in search results. Someone searches in Estonian and is shown the Russian version of your page. They do not switch language — they go back to the results and click a competitor instead. Hreflang reduces the chance of that swap.

Second, signal consolidation. Google treats hreflang-linked pages as one entity across languages rather than several independent documents. Link and behavioural signals accumulated by one version work for the whole cluster.

What hreflang does not do:

  • it does not substitute for genuinely good content in that language — if a page is machine-translated and misses the actual query wording, no markup will save it (I covered why in the article on which language to target for SEO in Estonia);
  • it does not protect against duplicate content — language versions are not duplicates in the first place;
  • it is not a directive. Google describes hreflang as a signal it takes into account, not one it is obliged to follow.

What correct markup looks like

Take a service page on a site with three languages. The <head> of all three versions must carry the same set of tags:

<link rel="alternate" hreflang="et" href="https://example.ee/teenused/seo" />
<link rel="alternate" hreflang="ru" href="https://example.ee/ru/uslugi/seo" />
<link rel="alternate" hreflang="en" href="https://example.ee/en/services/seo" />
<link rel="alternate" hreflang="x-default" href="https://example.ee/teenused/seo" />

The key point: the set is identical on all three pages, including the page's reference to itself. That is not redundancy — without the self-reference the relationship counts as incomplete.

There are three places to put the markup, and the choice does not affect the result:

MethodWhen it suitsLimitation
Tags in <head>Ordinary HTML pages, most sitesBloats <head> when you have many languages
XML sitemapMany languages, or no access to the templateHarder to verify by eye
HTTP Link headerPDFs and other non-HTML filesConfigured at server level

Choose on one criterion: can you generate it automatically. Hreflang added by hand at publication time falls apart somewhere around the second hundred URLs — that is a matter of time, not of diligence.

On this site the markup is generated from a single registry of slug mappings, and language alternates are only emitted for pages that actually exist. That is a deliberate constraint: better to serve no hreflang at all than to point at a page that is not there.


The six mistakes I see most often

Below is what I regularly find on multilingual Estonian sites during a technical audit, roughly in order of frequency.

MistakeHow it shows upWhat to do
Non-reciprocal linksGoogle ignores hreflang across the whole clusterGenerate markup from one source, not page by page
No self-referenceThe relationship counts as incomplete and is discardedAdd a hreflang for the page itself in every version
Relative URLsThe markup is not parsedAbsolute URLs only, with scheme and domain
Link to a redirect or 404The relationship breaks silentlyCheck status codes of every URL in the markup with a crawler
Conflict with canonicalCanonical points at another language version, cancelling hreflangEach page's canonical points at itself
Automatic redirect by browser languageGooglebot only ever sees one versionOffer the language with a banner, do not force a redirect

The conflict with canonical deserves separate attention — it is the most insidious of the six, because each piece of markup looks correct in isolation. If the Russian version of a page carries a canonical pointing at the Estonian one (a common result of copying a template), you are telling Google both "this is an independent language version" and "this is a copy of another page, do not index it". Google follows the canonical and the Russian version simply drops out of the index. I covered similar drop-out scenarios in the article on why Google is not indexing your pages.

One more thing specific to Estonia: a language code is not a country code. hreflang="ee" is a common typo, because the .ee domain is right there in front of you. There is no language code ee — Estonian is et. Such a tag is simply ignored and the markup becomes incomplete.


How to verify Google accepted it

Check on three levels, and the first two do not replace the third.

  1. Page source. Open the page through "view page source", not the element inspector. If the tags only appear in the inspector, JavaScript is injecting them after load and the markup may never reach the index. Check one page of each type this way: home, category, article, product.
  2. Crawler. Screaming Frog or equivalent walks the site and surfaces what you cannot find by hand: non-reciprocal pairs, links to redirects, missing self-references, canonical conflicts. It is the only practical way to check a site with more than about fifty pages.
  3. Search Console. The final authority: it shows what Google accepted, not what you served. Look at the coverage report and crawl stats — if pages moved into "Duplicate, Google chose a different canonical" after you deployed hreflang, you have a canonical conflict.

Expect the changes to take weeks rather than days to register: Google has to recrawl every page in the cluster to see that the links are reciprocal. It is the same delayed-reaction logic I described when explaining why changing hosting does not hurt SEO — changes are only visible after a recrawl.


What I see in practice

A few observations from Estonian projects, without numbers where I do not have precise ones.

Hreflang is almost never the reason traffic drops. When a second language version brings no visitors, in the overwhelming majority of cases the problem is not the markup but that the pages were translated word for word and miss the query wording of their own language. Hreflang gets checked first because it is quick and legible, while the real problem sits in the semantics.

Broken hreflang, on the other hand, is almost always broken entirely. I rarely meet a site where the markup is correct on 90% of pages: either it is generated from code and works everywhere, or it was added by hand and has fallen apart almost everywhere. There is practically no middle state — and that is good news, because what needs fixing is the generation, not individual pages.

Limits of applicability. Everything above is tested on sites of up to a few thousand pages with two or three languages. On large multi-regional projects, where a dozen countries are layered on top of language, region priorities introduce their own complications and the verification approach is different.


What to do right now

A short sequence if you have a second language version and are unsure about the markup.

  1. Open the page source of one page from each version and compare the tag sets — they must match completely, including the self-reference.
  2. Check the canonical on the non-primary language versions: it must point at the page itself, not at the primary language.
  3. Run a crawler over the site and filter for non-reciprocal relationships and URLs returning anything other than 200.
  4. Remove any automatic redirect based on browser language and replace it with an unobtrusive banner offering to switch.
  5. Confirm the markup is generated rather than hand-written. If it is hand-written, that is the first thing worth rebuilding at the web development stage, while the page count is still small.

If the picture still does not add up after those five steps, or Search Console is showing something ambiguous, write to me. Reviewing multilingual markup usually takes a couple of hours and is part of a technical SEO audit; more often than not the problem turns out not to be hreflang at all, but it is worth finding out before you invest in content for the second version.

Frequently asked questions

Does hreflang affect Google rankings?
Not directly. Google states that hreflang is not a ranking factor: it helps decide which language version to show a particular user. There is an indirect effect: when users see a page in their own language they return to the search results less often, and accumulated signals are distributed across versions more accurately. But do not expect ranking gains from hreflang alone.
Does hreflang have to be reciprocal?
Yes, this is a hard requirement. If page A points to page B as a language version but B does not point back to A, Google ignores the relationship. In practice this usually disables hreflang for the whole cluster of pages rather than for one link. That is why the markup must be generated from a shared source of truth instead of being added manually page by page.
Do I need x-default and what should go in it?
X-default is optional but useful: it declares the fallback version for users whose language does not match any of the listed ones. It normally points either to the site's primary language version or to a language selector page. For an Estonian business running Estonian, Russian and English versions it makes sense to point x-default at the primary version rather than build a separate selector page.
Should hreflang use language only, or language plus country?
If you split your audience by language, a language code is enough: et, ru, en. A country code such as ru-EE is only needed when you have separate versions of the same language for different countries — for example a Russian version for Estonia and another for Latvia with different pricing. Adding a region without that need is just an extra source of errors, and a country code without a language code is invalid entirely.
How do I check that hreflang is working?
Check on three levels. First the page source: confirm the tags are actually served in the HTML rather than injected by JavaScript after load. Second a crawler such as Screaming Frog, which surfaces non-reciprocal links, links to redirects and 404s. Third and most important the Search Console coverage report, which reflects what Google actually accepted rather than what you served.
Does content in different languages count as duplicate content?
No. Language versions of the same page are not duplicates, there is no penalty for them, and hreflang is not needed to protect against them. The real problem is cannibalisation within a single language, where two pages in the same language compete for the same query. Hreflang does not help there: you need to separate the pages by intent or consolidate them with canonical.

Conclusion

Hreflang is hygiene for a multilingual site, not a growth lever. Set up correctly it will not win you rankings, but it prevents the situation where an Estonian-speaking searcher is shown the Russian version of your page and leaves before finishing the title. Generate it from code once rather than maintaining it by hand, and check the Search Console report after every change to your URL structure. If you run two or three language versions and are not sure Google has linked them together, write to me — I will look at the markup and tell you where it breaks.

About the author

Vladislav Krivorutsko — founder of ADLAB
Vladislav Krivorutsko

Founder of ADLAB OÜ · SEO and Google Ads

Over 20 years in search traffic and monetization, and on the Estonian market since 2017. I work solo: I run the audit, build the strategy and deliver the project myself — no subcontractors, no templates. I only write about what I have tested on my own and client sites.

  • 20+ years in search traffic
  • 50+ end-to-end projects
  • Own sites in competitive niches
  • SEO for ru/et/en in one market
More about me

Read next