If you publish the same content in two or more languages, you have a problem Google needs help with: which version goes to which user. Without explicit signals, Google might decide the English page outranks the Italian one in Italy, or that the two pages are near-duplicates and only one deserves to be indexed at all. Hreflang is the small set of HTML tags that fix this.

The official documentation is long and full of edge cases. The working version of the rules is short.

What hreflang actually does

For each page that has a translation, you list every language version (including itself), each with a language code. Google reads those tags, builds a "cluster" of equivalent pages, and serves whichever one matches the user's browser language and location.

A page with hreflang tags does not rank higher because of them. It just gets served to the right person. The SEO benefit is indirect: lower bounce rate from users landing on the wrong language, no duplicate-content penalty between translations, faster crawling because Google understands the structure.

The three rules you cannot break

Rule 1. Self-referencing. Every page must list itself in its own hreflang block. The English page lists English plus Italian. The Italian page lists Italian plus English. If the page does not include itself, the whole block is ignored by Google.

Rule 2. Reciprocity. If page A says "my Italian version is at URL B", page B must say "my English version is at URL A". Asymmetric blocks are silently dropped. This is the most common bug in real sites: somebody adds a translation, links it from the original, but forgets to add the back-link.

Rule 3. Absolute URLs. Hreflang values must be full URLs (https://example.com/it/page), never relative paths (/it/page). Google will refuse the relative ones.

If your site respects those three rules, hreflang works. Most other complications come from people who don't.

The codes you need to know

The hreflang value is either:

  • A language code: en, it, fr, de, es. Two letters, ISO 639-1.
  • A language plus a region: en-US, en-GB, pt-BR, pt-PT, es-ES, es-MX. Region uses ISO 3166-1 alpha-2.
  • The special value x-default, meaning "use this page if no other version matches".

When to use the simple language code (en) vs the region-qualified form (en-US):

  • Use the simple language code if your content is the same for all regions speaking that language. One Italian version for all Italian speakers, one English version for all English speakers. This is what most sites should do.
  • Use the regional form only if you have actually different content per region: prices in different currencies, different shipping policies, different legal text. en-US and en-GB should differ in something concrete, not just the spelling of "colour".

x-default is for the page Google should serve to users whose language doesn't match any of your translations. Usually it points to your English version, which is also your en page. That is correct: a single URL can be both en and x-default.

How to add the tags

Three places, in order of preference.

HTML head: simplest, recommended for most sites. Each page includes:

<link rel="alternate" hreflang="en" href="https://example.com/en/page">
<link rel="alternate" hreflang="it" href="https://example.com/it/page">
<link rel="alternate" hreflang="x-default" href="https://example.com/en/page">

These three lines go on both the English and the Italian page. Identical block, on every translation.

HTTP headers: useful for non-HTML resources like PDFs, where you cannot put a <link> tag. Same information, different syntax:

Link: <https://example.com/en/page>; rel="alternate"; hreflang="en",
      <https://example.com/it/page>; rel="alternate"; hreflang="it"

XML sitemap: useful when you have hundreds of translated pages and you want to keep the markup out of the HTML. Each <url> entry contains the alternate language links. Same logic as the HTML version, just in sitemap format.

Pick one method and stick with it. Mixing two gives Google contradictory signals.

What goes wrong

The list of failure modes, in rough order of frequency:

  1. Forgot the self-reference. The Italian page lists only the English version, not itself. Google drops the whole cluster.
  2. One direction only. English links to Italian, Italian forgets to link back. Google ignores the asymmetric pair.
  3. Wrong URL canonicalization. The hreflang points to https://example.com/en/page/ (with trailing slash) but the actual page is at https://example.com/en/page (without). Google sees a 301 and considers the link broken.
  4. Mixing canonicals. The English page has <link rel="canonical" href="https://example.com/it/page"> (pointing to the Italian page). This says "the Italian page is the real one, ignore me". Hreflang and canonical contradict, canonical wins, your English version disappears from the index.
  5. Inconsistent code. Using en-uk instead of en-GB. UK is not the country code, GB is. Google rejects unknown codes.
  6. Codes that don't exist. Using gb, uk, eu as language codes. Language codes are ISO 639-1 (two letters), region codes are ISO 3166-1 (two letters). They are different lists.

How to verify: Google Search Console has an "International Targeting" report (under Legacy tools). It tells you which clusters are correctly set up and which have errors. For one-off checks, browser extensions like "Hreflang tags checker" inspect the tags on the current page.

A minimal working example

A site with English and Italian, where the Italian translation of /about lives at /it/about, the English at /en/about, and the bare /about redirects to /en/about:

On https://example.com/en/about:

<link rel="canonical" href="https://example.com/en/about">
<link rel="alternate" hreflang="en" href="https://example.com/en/about">
<link rel="alternate" hreflang="it" href="https://example.com/it/about">
<link rel="alternate" hreflang="x-default" href="https://example.com/en/about">

On https://example.com/it/about:

<link rel="canonical" href="https://example.com/it/about">
<link rel="alternate" hreflang="en" href="https://example.com/en/about">
<link rel="alternate" hreflang="it" href="https://example.com/it/about">
<link rel="alternate" hreflang="x-default" href="https://example.com/en/about">

Note the canonical is self-referencing on each page. The hreflang block is identical on both. The x-default is the same on both, pointing to the English version.

That is the entire setup. Copy it for every translated page, change the slug, and you are done. Real-world variants (more languages, regional sub-versions, automatic generation in PHP, sitemap-based) are mechanical extensions of this same pattern.

What hreflang doesn't do

Common misconceptions worth dispelling:

  • It does not redirect users automatically. Hreflang tells Google what to serve in search results. It does not affect what your server does when a user types the URL directly.
  • It does not affect ranking, only targeting. A bad page in Italian still ranks badly in Italy. Hreflang only ensures it is served to the Italian user instead of the English page.
  • It does not solve the "which language do I show by default" problem. That is a separate decision (Accept-Language header, IP geolocation, manual switcher), with its own trade-offs.

The good news: once hreflang is right, you forget about it. The bad news: getting it right requires that every translation, on every page, links to every other version, in every direction. The bookkeeping is the actual work.