Docket / Fix it / hreflang return tags

How to fix hreflang return tags

hreflang is the only common SEO tag that cannot be validated by looking at the page it sits on. Every tag can be present, spelled correctly and pointing at a live URL, and the whole set can still be ignored, because the rule is not about the tag, it is about the pair.

The rule, in one sentence

If page A declares page B as an alternate, B must declare A in return. A declaration that goes one way is discarded. Not downgraded, not partially honoured: ignored, and the wrong language version keeps being served to the wrong country.

This is why it is the hardest hreflang error to find by eye. Open the English page: the tag block is there, the codes are valid, the URLs resolve. Open the German page: same. The defect exists only in the relationship between them, and nothing on either page shows it.

What a complete set looks like

Every page in the group lists every page in the group, including itself. That self-reference is the part people leave out, and leaving it out breaks the set:

<link rel="alternate" hreflang="en-gb" href="https://example.com/en-gb/shoes" />
<link rel="alternate" hreflang="de-de" href="https://example.com/de-de/schuhe" />
<link rel="alternate" hreflang="x-default" href="https://example.com/shoes" />

That identical block goes on all three URLs. Not a tailored block per page: the same one. Generating it once and including it everywhere is the only approach that survives contact with a growing site, because the failure mode of hand-maintenance is silent.

The four ways a set breaks

hreflang and canonical must agree

This is the conflict that undoes otherwise-correct setups. Each language version should canonicalise to itself. If the German page carries a canonical pointing at the English one, you have told search engines two contradictory things: "these are equivalent alternates" and "the German one is a duplicate that should not be indexed". The canonical wins, the German page drops out, and the hreflang set silently loses a member.

Self-canonical on every language version. Always. The other ways that tag goes wrong ( two on one page, one pointing at a noindex page) are in how to fix conflicting canonicals.

Which code to use

Language alone (de) targets speakers of that language anywhere. Language plus region (de-at) targets speakers in one country. Use the pair only when the pages genuinely differ by country: different prices, currency, shipping, legal text. Two pages that differ by nothing but a flag icon do not need separate regional targeting, and splitting them divides whatever authority they have earned.

x-default names the page for everyone the set does not cover. A language selector or a global landing page is the right target; your busiest market's homepage is not, because it will be served to people it was not written for.

How to check it, and why the check has to crawl

Any tool that validates hreflang by reading one page can only tell you the codes parse. To know whether a set is reciprocated, something has to fetch every page in the set and compare the declarations against each other. Docket does that during the crawl and reports the specific pair that fails (which page points where, and which page does not point back) because "hreflang errors: 4" is not something anybody can act on.

It reports this at HIGH severity for a reason: a broken set is not a missed optimisation. It is a feature that is fully implemented, looks correct on inspection, and does nothing.

Common questions

What is an hreflang return tag?

If page A lists page B as a language alternate, B must list A in return. That reciprocal declaration is the return tag. Without it the declaration is one-way and search engines ignore it.

Does every page need to list itself in hreflang?

Yes. Each page in the set must include a self-referencing hreflang entry alongside the entries for the other versions. Omitting it is the most common way a set breaks.

Can hreflang and canonical tags conflict?

They can, and it is the conflict that undoes most correct-looking setups. Each language version must canonicalise to itself. A canonical pointing at another language says that page is a duplicate, which removes it from the set.

Why can't I spot this by viewing the page source?

Because nothing is wrong on the page you are looking at. Every tag can be present and valid in isolation; the error exists only in the relationship between two pages, so finding it requires fetching both and comparing them.