Originally published on the WeLocale blog.
Most SEO work is a matter of degree. You improve a title, you gain a little. hreflang is not like that. It either forms a valid set that search engines act on, or it does nothing at all, and the failure is completely silent. No warning, no penalty, no message in Search Console telling you the tags you carefully added are being discarded.
We build a translation widget, which means we generate hreflang tags for other people's sites. This post is what we have learned about why they get ignored, including the parts where our own approach has real limits.
The rule that breaks most setups
hreflang is not a property of a page. It is a property of a set of pages, and every page in that set has to agree.
If your English page says the German version is at /de/, the German page has to say the English version is at /. If it does not, the declaration is one-way, and one-way declarations get dropped. Google calls these return links and treats their absence as a reason to distrust the whole set.
This is why hreflang fails in a way that feels unfair. Every individual page looks correct when you inspect it. The problem only exists in the relationship between pages, which is exactly the thing you cannot see by viewing source on one URL.
The corollary catches people too: each page must list itself. A German page whose tags mention English and French but not German is an incomplete set. Incomplete sets get dropped.
The other four failure modes
en-UK. The language code comes from ISO 639-1 and the region code from ISO 3166-1. In ISO 3166-1 the United Kingdom is GB. There is no UK. The tag is silently invalid, and it is easily the most common hreflang error on the web. Same class of mistake: lowercase regions, uppercase languages, and a region with no language at all.
URLs that redirect. hreflang has to point at the final URL. If it points at http and you redirect to https, or it omits a trailing slash your server adds, the target is a redirect rather than a page, and it does not count. This one bites hardest after a migration, when everything still works fine for humans.
Targets a crawler cannot use. A page blocked in robots.txt, or carrying noindex, or canonicalised somewhere else, cannot be an hreflang target. The canonical conflict is the quiet one: your hreflang says these five pages are alternates of each other, your canonical tag says four of them are copies of the fifth. Both look right in isolation and together they cancel out.
Mixing declaration methods. You can declare hreflang in HTML link tags, in HTTP headers, or in an XML sitemap. Pick one. Declaring the same page's alternates in two places is a reliable way to produce conflicting signals.
What client-side injection can and cannot do
Here is the part where I have to be straight about our own product, because it is directly relevant.
Our widget injects hreflang tags from JavaScript, after the page loads. That has consequences worth understanding before you rely on it, whoever your vendor is.
Google renders JavaScript, so tags added this way can be picked up. But rendering happens in a second pass, separately from the initial crawl, and it is not guaranteed to happen on any particular schedule. Server-rendered tags are simply stronger. Other search engines are less reliable about running JavaScript at all.
The second consequence is the URL shape. We build alternates as a query parameter, ?lang=de, because we do not create a separate page per language. That is a deliberate trade: nothing gets duplicated in your CMS, and you do not re-translate when you edit a paragraph. What you give up is a clean path per language. A query parameter can be indexed, but a path like /de/ is a stronger signal, and tools that translate URL slugs will beat us on this specific point.
So if multilingual organic search is the main reason you are translating your site, and you want each language on its own indexable path, a tool that produces real per-language URLs fits better than we do. GTranslate serves translated sites from its own network with translated URLs. WordPress plugins such as WPML and Polylang create a post per language. Both are the right answer for that requirement, and I am not going to pretend otherwise.
Where a runtime widget makes sense is the other case: you want visitors to read the site in their language, you do not want a second copy of every page, and hreflang is a useful signal rather than the entire strategy.
How to check what you actually have
Three steps, in order of how often they find something.
First, view source on two different language versions and compare the blocks. Not one page. Two. You are checking that they reference each other and that each includes itself. This finds the return-link problem, which no single-page checker can.
Second, take every URL in the set and request it directly. Any redirect, any 404, any noindex, and that entry is dead.
Third, check that your canonical tags agree with your hreflang set rather than fighting it.
If you want the tags themselves generated and syntax-checked, we built a free hreflang generator and validator. It runs entirely in your browser, needs no account, and outputs HTML tags, an HTTP header or an XML sitemap block. It catches en-UK, case errors, relative URLs, duplicate pairs and a missing x-default. It cannot check return links, because that is a property of your whole site rather than of any tag, and a tool claiming otherwise would be lying to you.
Should you use x-default?
It is optional and worth setting. x-default tells a search engine which page to serve a visitor whose language you do not cover. Usually that is your main page or a language selector. Without it, that visitor gets whichever version the engine guesses, and the guess is often wrong for exactly the people you understand least.
If you have hit an hreflang failure mode that is not on this list, I would genuinely like to hear it.
This article was originally published by DEV Community and written by Serghei Streltov.
Read original article on DEV Community