The problem in plain words
You publish an article in English at /blog/post. You translate it to Spanish and publish it at /es/blog/post. Same idea, different language. To you, two pages serving two audiences. To Google, two URLs with very similar structure, similar imagery, similar internal links, and the same author.
Without a clear signal from you, Google has to guess. Sometimes it picks one and shows it to everyone. Sometimes it folds them together as duplicates. Sometimes it serves the Spanish version to a reader in New York. None of this is what you want.
Hreflang is the signal. It tells search engines which language a page is in, and which other URLs are the equivalent in other languages. Combined with a clean canonical, it stops the two pages from competing and lets each one rank in front of the right audience.
This is the same kind of clarity work that lives in a solid semantic hierarchy: you make the structure obvious so the crawler does not have to invent meaning.
What hreflang actually says
A hreflang tag is a small declaration. It looks like this in the head of an HTML page.
<link rel="alternate" hreflang="en" href="https://aldeacode.com/blog/post" />
<link rel="alternate" hreflang="es" href="https://aldeacode.com/es/blog/post" />
<link rel="alternate" hreflang="x-default" href="https://aldeacode.com/blog/post" />
In plain English, those lines say: “the English version of this content is at the first URL, the Spanish version is at the second URL, and if the visitor’s language does not match either, send them to the default.”
You put the same block on both pages. The English page declares its English URL and its Spanish URL. The Spanish page declares its English URL and its Spanish URL. Both pages reference both URLs. That part is not optional, and it is the part most people get wrong.
The reciprocity rule
Every hreflang relationship has to be mutual. If page A says “my Spanish equivalent is page B”, then page B has to say “my English equivalent is page A”. If only one side declares the relationship, Google ignores both declarations.
This is why a half-finished migration breaks everything. You add hreflang to the new pages, you forget to add it to the old ones, and the result is that none of your hreflang signals count. The pages keep competing.
A practical check: before you ship hreflang, list every URL pair on a spreadsheet. For each pair, confirm that both URLs declare both alternates and that both URLs declare themselves. Three lines per page, on every page in the set. If the spreadsheet is incomplete, the implementation is incomplete.
Self-reference matters
Each page has to include a hreflang link pointing to itself. The English page lists its own English URL. The Spanish page lists its own Spanish URL. This is what tells Google “this is the English version, not just a page that happens to know about an English version”.
Skipping the self-reference is a quiet failure. Search Console will not always shout about it, but the signal is incomplete and Google may not act on it.
The x-default value
x-default is a fallback. It does not mean “English”. It means “any visitor whose language preference does not match any of the other declarations”.
The most useful place for x-default is the homepage of a multi-language site. A visitor lands on the root, their browser language is Polish, you do not have a Polish version: where do they go? x-default answers that question. You point it at the URL you want them to see when none of the explicit options fit.
For deep pages with two or three language versions, x-default is less critical. For top-level pages on sites that target many regions, it is part of the basic kit.
Language codes, done right
Hreflang uses ISO codes. Two letters for language (en, es, fr, de), and an optional two-letter region after a hyphen (en-US, en-GB, es-ES, es-MX).
A few common mistakes:
- Inventing codes. There is no
ukfor British English. The code isen-GB. There is nocnfor Chinese. The codes arezh-Hansfor simplified andzh-Hantfor traditional. - Using only the language when the content is region-specific. If your English page lists prices in dollars and ships from a US warehouse, that is
en-US, noten. A reader in Manchester sees the dollar prices and bounces. - Using only the region.
en-GBworks.GBalone does not. Language always comes first. - Mixing case inconsistently. The convention is lowercase language and uppercase region:
en-GB, noten-gborEN-GB. Search engines tolerate variance, but consistency makes the implementation easier to audit.
If your site is one global English version with no regional differences, en is fine. If you have separate US and UK versions with different copy, prices, or stock, use the regional codes and serve different content. Half-measures here are worse than no hreflang at all.
Canonical alongside hreflang
This is where teams trip themselves up most often. The canonical tag is per-language. The English page is its own canonical. The Spanish page is its own canonical.
<!-- on /blog/post -->
<link rel="canonical" href="https://aldeacode.com/blog/post" />
<!-- on /es/blog/post -->
<link rel="canonical" href="https://aldeacode.com/es/blog/post" />
A common mistake is to set the English URL as the canonical for the Spanish page, in the belief that this consolidates “duplicate” signal. It does the opposite. Telling Google that your Spanish page is canonically the English one is telling Google not to index the Spanish page. The hreflang and the canonical contradict each other, and Google resolves the contradiction by following the canonical.
The clean rule: each language version is its own canonical, and hreflang declares the relationships between them. Canonical handles within-language duplication (a page reachable at two paths, a printable variant, a tracking-parameter URL). Hreflang handles between-language equivalence.
Tags or sitemap?
You can declare hreflang two ways: per-page <link> tags in the HTML head, or <xhtml:link> entries inside each <url> block in your sitemap.
The page-level approach is fine for small sites. Two languages, fifty pages, you can keep it consistent. The sitemap approach is much easier to maintain at scale. One file, one place to update, no risk of forgetting a tag on a single page that breaks reciprocity for a whole pair.
A minimal sitemap entry for a hreflang pair looks like this.
<url>
<loc>https://aldeacode.com/blog/post</loc>
<xhtml:link rel="alternate" hreflang="en" href="https://aldeacode.com/blog/post" />
<xhtml:link rel="alternate" hreflang="es" href="https://aldeacode.com/es/blog/post" />
</url>
<url>
<loc>https://aldeacode.com/es/blog/post</loc>
<xhtml:link rel="alternate" hreflang="en" href="https://aldeacode.com/blog/post" />
<xhtml:link rel="alternate" hreflang="es" href="https://aldeacode.com/es/blog/post" />
</url>
Same reciprocity rules apply. Both URLs list both alternates.
How to verify the implementation
Google Search Console is the canonical place to check. Two tools matter.
The first is the URL Inspection tool. Paste a URL, expand the “Page indexing” section, and Google will tell you what hreflang it picked up and whether the alternates are valid.
The second was historically the International Targeting report. Google has reshuffled this UI a few times, so the exact menu name may differ when you read this. The signal you are looking for is the “no return tags” error. That error means page A declared page B as an alternate, but page B did not declare page A back. Reciprocity is broken on that pair, and the fix is on the page that did not return the tag.
Run a recheck after every deploy that touches templates, language switchers, or sitemap generation. The bug pattern is almost always “we updated one side and forgot the other”.
Common mistakes to avoid
- Country flags as language indicators. A flag is a country, not a language. A reader in Mexico City does not feel served by a Spain flag. A reader in Quebec does not feel served by a France flag. Use the language name in the language itself: “Español”, “Français”, “English”. Flags are decoration at best, alienating at worst.
- Using only language codes for region-specific content. If your prices, shipping options, or legal copy differ by region, the region code is part of the meaning.
en-USanden-GBare different versions, not duplicates. - Forgetting x-default on the homepage. Every visitor whose language is not in your set has to land somewhere. Decide where, declare it, and stop guessing.
- Mixing canonical and hreflang. Each language is its own canonical. Hreflang is the bridge between languages. They do different jobs. They should never contradict.
- Half-finished migrations. Adding hreflang to half a site is worse than not adding it at all. Reciprocity has to be complete or Google ignores it.
The same care that goes into Core Web Vitals or a tight performance budget belongs here. International SEO is not glamorous work. It is small declarations on every page, kept consistent over time. Done well, you stop fighting yourself in the SERPs.
Frequently asked questions
Do I need hreflang if my site is single-language? No. Hreflang only matters when you have two or more language or regional versions of the same content. A single-language site uses canonical alone.
What is the difference between hreflang and the lang attribute?
The lang attribute on the <html> element tells the browser and assistive technology what language the current page is in. Hreflang tells search engines about other language versions of the same content. They serve different audiences. Set both.
Can I use hreflang for content that is not a translation? Hreflang is for the same content in different languages or regions. If two pages target different audiences with different content, they are not hreflang alternates. They are different pages.
How long does it take for Google to apply hreflang changes? Days to weeks. Search Console will pick up the new tags on the next crawl, but the effect on which version ranks where takes longer. Do not panic on day three.
What if my translations are partial? Only declare hreflang for URLs that exist and are actually equivalent. Declaring a URL that returns 404 or that contains very different content will hurt you. Roll out hreflang as the translations land.