← All posts
Development3 Feb 2026· 4 min read

Hreflang Debugging: When Dutch Users See English Butter

~/blog/hreflang-debugging.md

A few months ago, our SEO team flagged something strange: Dutch users searching for a well-known butter brand were landing on English-language pages. Not occasionally — consistently. The hreflang implementation on one of our key product domains was broken, and it had been broken long enough that we were losing meaningful traffic in the Netherlands.

This is the story of how we diagnosed it, what was actually wrong, and what I learned about the surprising fragility of hreflang in multi-market setups.

What hreflang is supposed to do

For the uninitiated: hreflang is an HTML attribute (or HTTP header, or sitemap annotation) that tells search engines which language and regional version of a page to show to which users. If you have example.com/en/butter and example.com/nl/boter, hreflang tags tell Google that Dutch users should see the second one.

Simple in theory. Diabolically tricky in practice.

<!-- On the English page -->
<link rel="alternate" hreflang="en" href="https://example.com/en/butter" />
<link rel="alternate" hreflang="nl" href="https://example.com/nl/boter" />

<!-- On the Dutch page -->
<link rel="alternate" hreflang="nl" href="https://example.com/nl/boter" />
<link rel="alternate" hreflang="en" href="https://example.com/en/butter" />

The key rule: hreflang must be reciprocal. If Page A says "my Dutch version is Page B," then Page B must also say "my English version is Page A." If this bidirectional confirmation is missing, Google ignores the whole thing.

Finding the break

Our first instinct was to check the hreflang tags directly. They looked fine — both pages referenced each other correctly. So why wasn't it working?

Three hours of debugging later, we found it. The issue was in the canonical tags.

The Dutch page had a canonical pointing to a slightly different URL — one with a trailing slash inconsistency:

<!-- The hreflang said -->
<link rel="alternate" hreflang="nl" href="https://example.com/nl/boter" />

<!-- But the canonical on that page said -->
<link rel="canonical" href="https://example.com/nl/boter/" />

One trailing slash. That's it. Google treats those as different URLs, so the reciprocal relationship broke silently. No error in Search Console. No warning. Just... ignored.

Hreflang debugging is 90% URL normalisation issues. The actual tag logic is rarely wrong. It's the URLs that lie.

The fix and the fallout

The fix itself was simple: normalise all canonical URLs and hreflang references to use the same format (no trailing slash, in our case). We deployed it, resubmitted the sitemaps, and within two weeks traffic patterns normalised.

But the real lesson was about our process. Here's what we changed:

Automated validation

We built a script that crawls our hreflang implementation nightly and checks for:

  • Reciprocal link completeness (every hreflang has a matching return link)
  • Canonical/hreflang URL consistency
  • HTTP status codes on all referenced URLs
  • Self-referencing hreflang tags (required but often forgotten)
def validate_hreflang_pair(page_a, page_b):
    """Check that two pages correctly reference each other."""
    a_refs = get_hreflang_refs(page_a)
    b_refs = get_hreflang_refs(page_b)
    a_canonical = get_canonical(page_a)
    b_canonical = get_canonical(page_b)

    issues = []
    if b_canonical not in a_refs.values():
        issues.append(f"{page_a} references {page_b} but URLs don't match canonical")
    if a_canonical not in b_refs.values():
        issues.append(f"{page_b} doesn't reference back to {page_a}")
    return issues

Deploy-time checks

Any change to URL structure or page templates now triggers hreflang validation as part of the CI pipeline. If the reciprocal links break, the deploy fails. We don't rely on humans catching this anymore.

What makes hreflang so fragile

The core problem is that hreflang creates a distributed contract between pages. Each page makes promises about other pages, and those promises all need to be consistent. In a system with hundreds of pages across dozens of markets, that's thousands of bidirectional relationships, each one capable of silently breaking.

Add to this that:

  • Different teams often manage different market sites
  • CMS platforms don't always expose hreflang configuration cleanly
  • URL structures drift over time as sites evolve
  • CDN or middleware layers can transform URLs in ways that create inconsistencies

The result is a system that's almost designed to break in ways that are hard to detect.

The takeaway

If you're running a multi-market site, hreflang isn't a "set it and forget it" implementation. It's infrastructure — and like all infrastructure, it needs monitoring, testing, and the occasional 3am debugging session when Dutch users complain about seeing English butter.


The trailing slash thing still annoys me. One character, silent failure, weeks of lost traffic. If there's a better metaphor for the fragility of web infrastructure, I haven't found it.

All postsNext →