SEO

Multilingual SEO: hreflang and canonical without collisions

A multilingual site declares each language version with hreflang and gives each one a canonical to itself. Without return links, Google may ignore the set.

Key points

  • Every language version lists itself and all the others, with absolute URLs.
  • If A declares B, B must declare A: without return links the annotations can be ignored.
  • hreflang is not a ranking factor: it picks the right version, and canonical stays per language.

A multilingual site declares every language version with hreflang, and every version must list itself and all the others. Without absolute URLs and return links Google can ignore the whole annotation group.

Three methods, one list

Google accepts hreflang in three places: a <link rel="alternate" hreflang="..."> element in the head, an HTTP Link header, or an xhtml:link block in the sitemap. The rules do not change with the method, and the methods can be combined. Google's documentation on localised versions, read on 1 November 2026, asks for URLs with scheme and host, and states that Google does not take the language from the lang attribute: it takes it from the content.

The rules you cannot skip

  • Every page lists itself together with all the other versions: a partial list is a broken group.
  • Alternate URLs are absolute, with https and host, never relative paths.
  • Return links are mandatory: if the Italian page declares the English one, the English one must declare the Italian one.
  • x-default names the neutral page or the language selector, and does not replace the other annotations.
  • Every version has a canonical to itself: a canonical to the other language deletes one of them.

Checking reciprocity

cat > hreflang.mjs ([^/g)].map((m) => m[1]).slice(0, 20);
for (const loc of locs) {
  const html = await (await fetch(loc)).text();
  const alts = [...html.matchAll(/hreflang="([^"]+)"/g)].map((m) => m[1]);
  const self = html.includes(`rel="canonical" href="${loc}"`);
  console.log(loc, alts.join(","), self ? "self" : "canonical?");
  await new Promise((r) => setTimeout(r, 500));
}
console.log(`${locs.length} pages checked`);
EOF
node hreflang.mjs

The script takes the first twenty URLs from the sitemap, reads each page's annotations and prints the declared languages next to the canonical. The 2 or 3 column is the number that matters: when the Italian page declares two languages and the English one three, the group is not reciprocal. On the EneUI site this happens on every pair: the sitemap declares en, it and x-default for each URL, while the Italian head declares two.

The EneUI catalogue

The bilingual structure covers the catalogue too: the 111,985 records under /components, the 1,556 blocks under /blocks and the 12,808 icons under /icons have an English and an Italian version on distinct URLs, and the 30 categories such as /blocks/category/table follow the same rule. One URL per language, one canonical per URL. Checked on 1 November 2026.

The limitation

hreflang does not improve ranking: it picks the version to show, it does not lift the page. Reciprocity also breaks easily, as in our case: the sitemap declares en, it and x-default, the Italian head does not, and Google documents that the annotations may then be ignored. A machine translation nobody reviewed finally produces two near-identical pages: hreflang does not make them different, and canonical does not fix content that was never localised.

Where to continue

The mistakes that break groups are in hreflang, the common errors; the App Router implementation is in i18n in the Next.js App Router; the relation with canonical is in canonical, the practical guide.

Frequently asked questions

Does hreflang improve ranking?

No. It shows the right language version to someone searching in that language. Ranking depends on content and links, not on the annotation.

Should I use language or region codes?

Language is required, region is optional: it, it-IT, en, en-GB. Use a region only when the content really changes for that country.

What goes in x-default?

The neutral page or the language selector, the one that serves anyone matching no version. It is not mandatory, but it avoids sending a visitor to the wrong language.

Can I declare hreflang only in the sitemap?

Yes. The sitemap is one of the three accepted methods and works on its own, as long as URLs are absolute and every page lists itself and the others.

Why does Google show the wrong version?

Usually because of a non-reciprocal group, a relative URL or a canonical pointing at the other language. Check those three in order, then request a fresh crawl.