SEO

hreflang: the common mistakes and how to find them

A wrong hreflang throws no error: Google drops the pair. The five recurring mistakes, the script that finds them and what no check can see.

Key points

  • hreflang without a self-reference or without a return link is dropped silently.
  • Canonical and hreflang must agree: every version declares itself.
  • An hreflang pointing at a redirect, a 404 or a noindex is not used.

A wrong hreflang produces no visible error: Google drops the annotation and serves whichever version it considers suitable, often the wrong one. Five mistakes recur, and three of them can be found with a script before deployment.

How the annotation is declared

It is declared with a link element in the head, or with xhtml:link inside the sitemap. Every page lists itself and all the other versions, with absolute URLs: a relative href is ignored. The language code follows ISO 639-1, the region ISO 3166-1 alpha-2, and x-default marks the fallback page when no language is the right choice.

One direction is not enough. When the Italian page lists the English one and the English page does not list the Italian one, Google treats the annotation as unconfirmed and may ignore it. The commonest variant is the missing self-reference: every page must also list itself.

Mistake 2: invented language codes

en-UK does not exist: the United Kingdom region code is GB. it-IT is valid but useless, because it without a region already covers Italian. A code such as en-EU matches no country and gets discarded. The comparison is case-insensitive, but the readable form stays lowercase for the language and uppercase for the region.

Mistake 3: a canonical that contradicts hreflang

Every language version declares itself as canonical. If the English page points its canonical at the Italian one, Google treats English as a duplicate and hreflang is left with two URLs aiming at the same place. Canonical and hreflang answer two different questions: which URL is the right one, and which language the reader needs.

Mistake 4: the target redirects or is noindex

An hreflang pointing at a URL that redirects, answers 404 or carries noindex is ignored. It happens when a translation changes slug and the annotation stays behind. The check is mechanical: follow every href and read the response status.

Mistake 5: hreflang on content that is not translated

hreflang links versions of the same page in different languages, not pages covering the same subject. Two different articles, one Italian and one English, are not alternates: they are two pages, and a normal link connects them.

Check it on the sitemap

cat > check-hreflang.mjs ([\s\S]*?)/g)].map((m) => m[1]);
const locs = new Set(blocks.map((b) => (b.match(/([^/) || [])[1]));
let broken = 0;
for (const block of blocks) {
  const loc = (block.match(/([^/) || [])[1];
  const alts = [...block.matchAll(/hreflang="([^"]+)" href="([^"]+)"/g)]
    .map((m) => ({ lang: m[1].toLowerCase(), href: m[2] }));
  for (const alt of alts) {
    if (locs.has(alt.href)) continue;
    broken++;
    console.log("NOT DECLARED " + alt.href + " (from " + loc + ")");
  }
  console.log(alts.map((a) => a.lang).join(",") + "  " + loc);
}
console.log(blocks.length + " URLs · " + broken + " alternates without a target");
EOF
node check-hreflang.mjs

The script reads the blog sitemap, collects the hreflang annotations for every URL and checks that each target is itself declared in the file. A URL cited and never declared is a broken pair: Google reads the annotation but finds no page to confirm it.

The EneUI catalogue

A language switcher is not an SEO component, but it is where the mistakes start: language-switcher-pill, MIT licence, changes the URL without reloading the page; language-selector-dropdown declares no licence in its record and needs checking before use. The navbar blocks show where that switcher ends up. Checked on 1 November 2026.

The limitation

hreflang is a signal, not a directive: Google may ignore it when the content is not truly equivalent. No validator sees that difference, because it takes reading both versions. Search Console reports pairs without a return link, not translations done badly.

Where to continue

How to split an index of URLs is in sitemap index or a single sitemap; the sequence for getting into the index is in getting a site indexed by Google. If the choice is between two stacks for a marketing site, the comparison is in Next.js or Vite for a marketing site.

Frequently asked questions

Does hreflang have to be in the head?

No. A link element in the head works, and so does xhtml:link in the sitemap. What matters is that every page lists itself and all the other versions.

Do I need hreflang on a single-language site?

No. hreflang links different languages of the same page: with one language there is nothing to link, and the canonical is enough.

What does x-default do?

It marks the fallback page for readers who match none of the listed languages, for example a language selector or a choice page.

Does hreflang improve ranking?

No, it is not a ranking factor. It shows the right version to the right reader, and a mistake costs experience rather than position.

How many languages can I declare?

There is no practical limit: each page lists every available version, with absolute URLs. Past about ten, manual checks stop working and a script becomes necessary.