Hreflang: Why It Breaks, How to Audit It, and How to Fix It

By · Published · Updated

Hreflang: Why It Breaks, How to Audit It, and How to Fix It

Hreflang is the directive most likely to be wrong on any international site I audit. More often than canonicals, more often than robots rules, more often than broken links. The spec fits on one page. The implementation surface grows every time a regional team adds a locale without telling anyone.

Here’s why it breaks, the exact markup mistakes I keep finding, and how to audit the whole thing as a graph instead of one page at a time.

Why hreflang breaks in practice

Hreflang is a bidirectional graph. Every page that declares a language alternate has to be acknowledged by the page it points at. If /en/product says its French version is /fr/produit, then /fr/produit has to say its English version is /en/product. If it doesn’t, Google ignores the pair.

That rule scales badly. Fifteen locales means every page in a cluster carries fifteen links, and every one of those has to be mirrored on fourteen other pages. Now spread that across three CMSs and four regional teams who each think they own “their” pages. One team adds a locale, updates its own templates, and forgets the other fourteen. Nothing errors. Google just quietly stops trusting the cluster and picks whatever page it wants for whatever country it wants.

The damage is rarely one bad line. It’s a language code typo plus a missing self-reference plus an x-default pointing at a redirect. Each one alone is survivable. Stacked together, the cluster is gone, and the first signal you get is someone asking why Brazilian traffic looks wrong this month.

The errors I find on almost every international audit

The most common failure by a wide margin. Page A points to B, C, and D. B points back to A but forgets C and D. C and D point at nothing. The missing legs don’t just fail on their own; they make the whole cluster unreliable.

<!-- /en/product -->
<link rel="alternate" hreflang="fr" href="https://site.com/fr/produit" />

<!-- /fr/produit: BROKEN, no link back to /en/product -->

2. Missing self-references

Every page in a cluster should list itself. Programmers skip this because it feels redundant. Google expects it anyway.

<!-- /en/product must also include itself -->
<link rel="alternate" hreflang="en" href="https://site.com/en/product" />

3. Invalid language and region codes

en-UK is not a thing. The region code is GB, so it’s en-GB. I’ve seen pt-BZ (Belize, not Brazil) where someone meant pt-BR, and en-EU, which has never been valid. Language codes come from ISO 639-1 and regions from ISO 3166-1 Alpha 2. People guess, the guess gets templated across thousands of pages, and Google drops every one of them without a warning.

4. x-default pointing at a localized page

x-default is the fallback for users whose language or region you don’t cover. It should point at a neutral page or a language picker. I audited a site where x-default pointed at /en-us/, so a user in São Paulo looking for Portuguese got bounced through a US redirect before landing where they should have started. Google gave up on the cluster, and the Portuguese pages were effectively invisible in Brazil for months.

<!-- Bad -->
<link rel="alternate" hreflang="x-default" href="https://site.com/en-us/" />

<!-- Better -->
<link rel="alternate" hreflang="x-default" href="https://site.com/" />

5. Canonical and hreflang disagreeing

Hreflang says https://site.com/fr/. The canonical on that page says https://site.com/fr with no trailing slash, and one of them 301s to the other. You’ve listed a URL in the cluster that the page itself says isn’t canonical, so Google ignores the signal. Hreflang targets should match canonical URLs exactly. If your canonicals are already messy, fix the broken canonical tags first, because hreflang built on top of a bad canonical graph will never validate.

6. Protocol and subdomain mismatches

Same class of bug as the trailing slash. Hreflang points at http://, the canonical is https://. Or one uses www. and the other doesn’t. Pick one version site-wide and make every hreflang target use it.

7. Two sources of truth

Hreflang in the HTML head and in the XML sitemap, with values that don’t agree. One team owns the template, another owns the sitemap generator, and nobody reconciles them. Pick one source and delete the other.

How to audit hreflang as a graph

Checking one page at a time tells you almost nothing, because the errors live in the relationships between pages. You need every hreflang tag on the site, resolved into clusters, before you can say anything useful.

  1. Crawl every URL that declares alternates. No sampling.
  2. For each declared alternate, confirm the target returns 200 and doesn’t redirect.
  3. Confirm the target declares a return link to the source.
  4. Confirm every page includes a self-reference.
  5. Validate every code against ISO 639-1 and ISO 3166-1.
  6. Check that x-default exists where you need one and points somewhere neutral.
  7. Compare every hreflang target to the canonical on that target, down to protocol, subdomain, and trailing slash.
  8. If you publish hreflang in the sitemap too, diff it against the HTML.

Doing this in a spreadsheet means writing lookup formulas against a list that’s stale before you finish. By the time you’ve checked return links by hand, marketing has launched another locale.

I run the crawl in Black SEO Analyzer. The internationalization module flags invalid language codes, duplicate hreflang entries, malformed hreflang URLs, and missing x-default on every page, and every page’s HTML, status code, and redirect target land in a local SQLite database. That makes steps 2, 3, 4, and 7 a short script: pull the alternate links out of each page, look each target up in the same table, and print every pair that doesn’t point back. The errors come out tied to the cluster they broke, which is the only format a programmer can actually fix.

Fix order

  1. Language and region codes first. They’re cheap, and invalid codes make everything else noisy.
  2. Return links and self-references second.
  3. x-default third.
  4. Reconcile with canonicals last.
  5. Re-crawl and rebuild the graph.

That last step matters. A single missed return link in a large cluster can undo the rest of the work, and you won’t see it by spot-checking the pages you just fixed.

Hreflang is annoying, but it’s deterministic. A clean graph gets used. A broken one gets ignored, and Google will keep serving the wrong language to the wrong country until somebody notices the traffic data looks strange and starts digging.

Want to see your hreflang graph the way Google does? Try BSA free for 14 days, every feature, no page limit, no credit card.

-Sethers

Discover hundreds of SEO Issues in Seconds

Without Monthly Subscriptions

Comprehensive technical SEO analysis powered by ML and 16 specialized modules. Optional AI-powered insights from Claude, GPT-4, or Gemini. Get actionable insights in seconds, and never pay monthly fees again.

Download Free Trial

I use AI to generate images for my posts and for general editing, updates, and ironically SEO purposes. I used to draw all of the images for my personal blog (taleas) myself, but as the volume of content I produce has increased, I've turned to AI tools to help create visuals that complement my writing. I go out of my way to generate images that look strange, and don't represent real people. If you ever want to chat about my use of AI, please reach out.