WriteMySEO / Blog / Why hreflang Clusters Break and How to Verify Them
SEO technology

Why hreflang Clusters Break and How to Verify Them

hreflang is a graph, not a tag. Learn how Google builds language clusters, the four failure modes that silently drop them, and how to validate the graph yourself.

Most hreflang problems are not syntax problems. The codes are valid, the tags render, the validator plugin shows green — and Google still serves the US page to users in Germany. That happens because hreflang is not a per-page tag that Google reads in isolation. It is a graph that Google assembles from many pages, and a graph with one broken edge quietly stops being a cluster.

The second misconception is about what hreflang buys you. It does not raise rankings, and it does not consolidate link signals the way rel=canonical does. Google describes hreflang annotations as a signal for choosing which variant to show to a given user, not a signal for whether the cluster ranks at all. If none of your locale variants ranks for a query, perfect hreflang changes nothing.

What hreflang actually does at serving time

The mechanism is a swap. Google indexes each locale URL separately. When one of them is about to be shown for a query, Google can substitute a different member of the same cluster if that member better matches the user's language and location signals. The ranking decision happens first; the swap happens second.

Two consequences follow directly:

Google also treats these annotations as hints rather than directives. That is documented, and it matters for debugging: a correct annotation that Google chooses to ignore looks identical, in your markup, to a correct annotation that Google honors.

Why bidirectional confirmation is the core rule

Google requires return links. If page A declares B as its de-de alternate, B must declare A as its alternate in return. Unconfirmed edges are dropped.

The reason is anti-abuse, and understanding it explains the strictness. Without confirmation, anyone could publish a page declaring your high-authority URL as their English alternate and try to get swapped into your position. Requiring both endpoints to agree means both sites have to opt in.

Practically, this turns a set of tags into a graph problem:

  1. Every page in a cluster must list every member, including itself (the self-referencing annotation).
  2. Every listed URL must list the listing URL back, under the correct code.
  3. If a URL is added to one page's list but not the others', you get a partially confirmed cluster where some swaps work and some don't — which is exactly the symptom that makes people think hreflang "works sometimes".

That last case is the most common real-world failure: a new locale launches, the generator updates the new pages and the homepage, and the older locale templates are still emitting the old list.

The canonical conflict that silently kills clusters

This is the single most frequent structural break. Each alternate URL must be self-canonical. If your /en-gb/ page carries rel=canonical pointing at /en-us/, you have told Google two contradictory things: these are the same page, index only one and these are different pages, swap between them. Canonicalization wins. The GB URL stops being an indexed canonical, and annotations pointing to it have nothing to point at.

Watch for the same conflict in disguised forms:

Separately: near-identical en-us/en-gb pages can still get folded into one duplicate cluster by Google. hreflang helps Google pick the right member of that cluster to display, but it is not a guarantee of separate indexation. If two locales differ only by a currency symbol, expect folding and plan around it.

Choosing a delivery method

There are three supported placements, and they are not equivalent operationally.

MethodBest forMain cost
<link rel="alternate"> in <head>Small clusters, fewer than ~10 localesPage weight grows as n per page, n² across the cluster; edits require a template deploy on every locale
HTTP Link: headerNon-HTML files (PDFs, feeds)Invisible in the browser; easy to lose at the CDN or during an origin change
XML sitemap xhtml:linkLarge or fast-changing clustersRequires sitemaps to be generated from a reliable source of truth

The n² arithmetic decides most cases. Twelve locales across 40,000 URLs means roughly 5.8 million annotations if you put them in HTML. In sitemaps, that same graph is generated from one table and costs nothing at render time. Pick one method per cluster and stick to it; mixing methods across locales is how you get half-confirmed graphs.

How to verify the graph yourself

Search Console's International Targeting report, which used to surface return-tag errors, no longer exists. There is no first-party hreflang error report. Verification is your job now.

Build it as a graph check, not a tag check. Crawl every locale, extract annotations from whichever source you use, then assert:

# pages: {url: {"annotations": {lang: url}, "canonical": url,
#               "status": int, "indexable": bool}}
for src, page in pages.items():
    if src not in page["annotations"].values():
        error(src, "missing self-reference")
    if page["canonical"] != src:
        error(src, "not self-canonical; alternates will be ignored")
    for lang, dst in page["annotations"].items():
        target = pages.get(dst)
        if target is None or target["status"] != 200 or not target["indexable"]:
            error(src, f"{lang} -> {dst} unreachable or non-indexable")
            continue
        if src not in target["annotations"].values():
            error(src, f"{lang} -> {dst} has no return link")

Then check code validity separately: ISO 639-1 language, optionally combined with an ISO 3166-1 Alpha-2 region. en-uk is invalid (the region code is gb). A bare region like us is invalid — a code with no language is not a language code. Include one x-default for your fallback or language-selector page, and give it return links like any other node.

For outcome verification rather than markup verification, use Search Console's page + country dimensions. Export clicks by page filtered to a target country. If /en-us/ is collecting meaningful clicks from Germany while /de-de/ exists and is indexed, the swap is not happening for those queries — which is a different investigation than a broken tag.

What to do this week

  1. Run the graph check above across your full locale set, not a sample. Sampling hides the partial-cluster failures that matter most.
  2. Fix canonical conflicts first. They invalidate whole nodes and are usually a single template bug.
  3. Confirm every alternate returns 200 and is indexable before worrying about code formatting.
  4. If you have more than about ten locales, migrate hreflang out of HTML and into sitemaps generated from one locale table, so adding a market is a data change rather than twelve template deploys.
  5. Add the validator to CI against your staging sitemaps, so a new locale can't ship a half-confirmed graph.
  6. Set expectations with stakeholders: correct hreflang changes which page appears, not whether the cluster ranks. If a market underperforms after the graph is clean, the problem is content or links in that market.
hreflanginternational seocanonicalizationcrawling

WriteMySEO produces marketing content, not legal, medical, financial, or compliance advice. Figures cited reflect publicly reported industry data at time of writing and shift over time.

Get started

We write this well about your industry, every month.

AI-drafted, human-reviewed SEO content on a flat subscription. Blog posts, metadata, schema, and internal links, shipped on a monthly rhythm.

See plans

More from the blog