SEO Infrastructure · Session 35

Hreflang Audit & Fix

We had ~8,436 broken cross-store hreflang alternates emitting across the four Brainzyme® Shopify stores — 95% of the damage concentrated on UK blog articles. This dashboard is the permanent record: what broke, why it broke, the fix we shipped, how to verify it, and what to do when new products or pages get added.

Deployed & verified 14 April 2026 UK / US / DE / FR v2 (FR long-handle patch)
Launching a new product or page? Jump straight to the idiot-proof SOP — three step-by-step playbooks covering everything you'd need to touch.
Go to the SOP →
On this page
1. Background & diagnosis 2. Strategy & the 4-tier fix 3. Where we are now 4. SOP — adding new pages ★ 5. The v2 FR patch 6. Should we unify slugs? 7. Banked artefacts 8. ClickUp tasks
~8,436
Broken alternates (before)
~95% on UK blog articles
0
Broken alternates (after)
Verified by cache-bypass fetch
4
Shopify stores fixed
UK · US · DE · FR
4
Tier fallback structure
Homepage · Product · Allowlist · Self

01Background & diagnosis

Hreflang tags tell Google which page serves which country / language. Our four stores (UK, US, DE, FR) are meant to cross-link to each other on shared pages (homepage, products, a handful of shared info pages) and self-link on store-unique pages (blog articles, collections, unlisted pages).

What was broken

The old theme.liquid block had a single blind fallback branch:

{% else %}
  <link rel="alternate" hreflang="en-GB" href="https://www.brainzyme.com{{ request.path }}">
  <link rel="alternate" hreflang="en-US" href="https://www.brainzyme.us{{ request.path }}">
  <link rel="alternate" hreflang="de-DE" href="https://www.brainzyme.de{{ request.path }}">
  <link rel="alternate" hreflang="fr-FR" href="https://brainzyme.fr{{ request.path }}">
{% endif %}

That tells Google "the same URL exists on every store — go crawl it." For product pages and the homepage, that was true. For UK's 2,622 /blogs/focus-learning-center/* articles, those URLs don't exist on DE, FR or US. Google crawled them, got 404s, and logged "missing return tag" errors against every one.

Why it mattered

The original brief missed the headline finding

The audit was originally scoped to fix two assumed defects (UK ELITE slug, FR www-missing). On inspection, both were non-defects — UK's ELITE alias 301s to the canonical handle before Liquid runs, and FR's canonical domain is legitimately non-www (inverted from UK/US/DE). The real problem — the blog-article fallback emitting ~8,000 bad alternates — was out of scope on the original brief.

02Strategy & the 4-tier fix

Replace the blind {% else %} fallback with a tiered structure that only emits cross-store alternates on pages that actually exist on all four stores. Everything else falls through to self + x-default.

TIER 1
Homepage
Condition: template == 'index'
Emits 5 tags: 4 cross-store + x-default to UK. Always correct because every store has a homepage.
TIER 2
Product pages
Condition: template contains 'product'
{% case product.handle %} over ELITE / PRO / ORIGINAL / COMBO. Each case enumerates every aliased handle per store. Fallback branch inside: self + x-default (catches unlisted products safely).
TIER 3
Mirrored pages
Condition: page template + page.handle in an 11-item allowlist (ingredients, reviews, science, subscription-continued, etc.). Emits 5 tags via request.path because these pages exist on all 4 stores with identical slugs.
TIER 4
Self-ref fallback
Everything else — collections, blog indexes, blog articles, unlisted pages. Emits only 2 tags: self + x-default pointing at self via canonical_url. This is the fix. Kills all ~8,000 broken alternates in one pass.

Why tiered, not path-matched?

The old fallback assumed path equivalence across stores. The new structure inverts the default: cross-link only when we know the other stores have the matching page, never by assumption.

FR canonical rule — DO NOT change

FR's canonical domain is https://brainzyme.fr (non-www). www.brainzyme.fr 301s to the non-www host. Every fr-FR alternate in all four snippets points to brainzyme.fr (no www). Switching FR to www would create a redirect-chain defect on every international signal.

03Where we are now

All four stores have the v2 snippet live in layout/theme.liquid section 43 as of 14 April 2026. Verified via cache-bypass HTML fetch (browser DOM cache is unreliable — always verify against raw HTML).

Page type UK US DE FR Expected
Homepage /55555 tags
ELITE product page55555 tags
PRO product page55555 tags
ORIGINAL product page55555 tags
COMBO product page55555 tags
/pages/ingredients55555 tags
/collections/all22222 tags (self-only)
Blog article22222 tags (self-only)

Verification method (for future spot-checks)

Browser DOM (view-source, document.querySelectorAll) can show stale edge-cached markup even after a hard refresh. Always bypass the cache via a Chrome console fetch:

fetch(location.href + '?nc=' + Date.now(), {cache: 'reload'})
  .then(r => r.text())
  .then(html => html.match(/<link[^>]*rel=["']alternate["'][^>]*hreflang=[^>]*>/gi));

04SOP — adding new pages

Three playbooks. Pick the one that matches what you're adding. Every step is copy-paste ready. If you finish and verification fails, scroll to the troubleshooting table at the bottom of this section.

What are you adding?
A new blog article (any store, any language)
NO CODE CHANGEFallback handles it automatically. Stop reading — you're done.
A new collection (any store)
NO CODE CHANGESame fallback. Ship it — no hreflang work needed.
A new single-store page (exists only on UK, or only on FR, etc.)
NO CODE CHANGEFallback emits self + x-default. Safe by default.
A new product live on all 4 stores
FOLLOW PLAYBOOK A~10–15 min of work. Skip and you lose cross-store SEO on the new product.
A new /pages/xyz live on all 4 stores (e.g. new testimonials page)
FOLLOW PLAYBOOK B~5–10 min. Slug must be identical on all 4 stores.
Renaming the slug of an existing product on one store
FOLLOW PLAYBOOK C~5 min. Keep the old handle as an alias for safety.
Pre-flight — before you start any playbook
  • You have write access to the Shopify theme on the stores you're editing
  • Your current theme exposes canonical_url, template, product.handle, page.handle in <head> (Be Yours 8.1.1 does — verified)
  • You can open a Chrome DevTools console (for cache-bypass verification after deploy)
  • You know the exact paste location: layout/theme.liquid under the comment <!-- 43) Adding the theme diversion code for SEO purposes -->
  • If editing from Claude: the structured data lives at F:/Claude Root/config/hreflang_snippets.json, the paste-ready Liquid at F:/Claude Root/hreflang-audit/corrected-snippets.md
Playbook A New product launched on all 4 stores

You've created (or are about to create) a product that lives on UK, US, DE, and FR. The hreflang snippet won't know about it until you add it to the product map. Example product below: “Focus Plus”.

1

Get the real handle from each store

Shopify stores the handle per-store. UK/US/DE usually use short English handles; FR uses long localised slugs. Do not guess them. Open each store's products JSON feed in your browser:

https://www.brainzyme.com/products.json?limit=250
https://www.brainzyme.us/products.json?limit=250
https://www.brainzyme.de/products.json?limit=250
https://brainzyme.fr/products.json?limit=250

Find your new product by title (Ctrl+F). Copy the exact "handle" value. You should end up with 4 handles (one per store).

Trap
The FR handle will look like a paragraph. Copy all of it — truncating breaks the match. This is the exact bug v1 shipped with.
2

Add the product to config/hreflang_snippets.json

Open F:/Claude Root/config/hreflang_snippets.json and add a new entry under product_map. Use a short ALL_CAPS code as the key:

"FOCUS_PLUS": {
  "handles": [
    "brainzyme-focus-plus",
    "brainzyme-focus-plus",
    "brainzyme-focus-plus",
    "brainzyme-focus-plus-long-french-slug-here"
  ],
  "urls": {
    "en-GB":    "https://www.brainzyme.com/products/brainzyme-focus-plus",
    "en-US":    "https://www.brainzyme.us/products/brainzyme-focus-plus",
    "de-DE":    "https://www.brainzyme.de/products/brainzyme-focus-plus",
    "fr-FR":    "https://brainzyme.fr/products/brainzyme-focus-plus-long-french-slug-here",
    "x-default":"https://www.brainzyme.com/products/brainzyme-focus-plus"
  }
}

x-default always points to the UK URL. fr-FR is always non-www (brainzyme.fr, no leading www).

3

Extend the Liquid {% case product.handle %} block in all 4 store snippets

Open F:/Claude Root/hreflang-audit/corrected-snippets.md. You'll see 4 store sections. For each of UK, US, DE, FR, add a new {% when ... %} clause inside the elsif template contains 'product' branch, above the {% else %} fallback.

The when clause lists every store's handle for this product (so the same clause catches the product regardless of which store Liquid is running on):

{%- when 'brainzyme-focus-plus', 'brainzyme-focus-plus-long-french-slug-here' -%}
  <link rel="alternate" hreflang="en-GB" href="https://www.brainzyme.com/products/brainzyme-focus-plus" />
  <link rel="alternate" hreflang="en-US" href="https://www.brainzyme.us/products/brainzyme-focus-plus" />
  <link rel="alternate" hreflang="de-DE" href="https://www.brainzyme.de/products/brainzyme-focus-plus" />
  <link rel="alternate" hreflang="fr-FR" href="https://brainzyme.fr/products/brainzyme-focus-plus-long-french-slug-here" />
  <link rel="alternate" hreflang="x-default" href="https://www.brainzyme.com/products/brainzyme-focus-plus" />

Paste this clause into all 4 snippets (UK, US, DE, FR). The URLs inside are the same across all 4 snippets — only the self-ref logic differs per snippet.

4

Deploy each store's snippet to its Shopify theme

For each of UK, US, DE, FR:

  1. Shopify admin → Online Store → Themes → Edit code
  2. Open layout/theme.liquid
  3. Find the comment <!-- 43) Adding the theme diversion code for SEO purposes -->
  4. Replace the entire block (from {%- if template == 'index' -%} through the matching {%- endif -%}) with the updated store-specific snippet
  5. Save
Tip
Do UK first, verify it, then the other three. If something's wrong, you'd rather find out on one store than four.
5

Cache-bypass verify on each store

Open the new product page on each store. Open Chrome DevTools → Console. Paste:

fetch(location.href + '?nc=' + Date.now(), {cache: 'reload'})
  .then(r => r.text())
  .then(html => {
    const tags = html.match(/<link[^>]*rel=["']alternate["'][^>]*hreflang=[^>]*>/gi) || [];
    console.log('Count:', tags.length);
    tags.forEach(t => console.log(t));
  });

Expected: 5 tags. One each for en-GB, en-US, de-DE, fr-FR, x-default. If you get 2 tags, the when clause didn't catch — most likely a handle typo. See troubleshooting.

6

Resubmit sitemaps in Google Search Console (optional but speeds up recrawl)

For each of the 4 GSC properties, Sitemaps → Submit /sitemap.xml. Google should re-index the new product with correct hreflang within 24–72 hours.

Playbook B New shared /pages/xyz on all 4 stores

A page that exists at the same URL path on all four stores (e.g. /pages/testimonials launched on UK, US, DE, FR simultaneously). These go into the mirrored-pages allowlist. Rule: slug must be identical on all 4 stores — if slugs differ per locale, this is actually a Playbook A case (product-style case block needed, not a simple allowlist entry).

1

Add the handle to mirrored_pages_allowlist

Open F:/Claude Root/config/hreflang_snippets.json. Add the new handle (without /pages/ prefix) to the mirrored_pages_allowlist array:

"mirrored_pages_allowlist": [
  "brain-issues-quiz",
  ...existing entries...,
  "testimonials"
]
2

Add the path to the allowlist branch in all 4 snippets

Open F:/Claude Root/hreflang-audit/corrected-snippets.md. Each snippet has a {% elsif template contains 'page' %} branch with a path check. Add the new path:

{%- elsif template contains 'page' and page.handle == 'brain-issues-quiz'
    or page.handle == 'ingredients'
    or page.handle == 'testimonials'  {%- comment -%}NEW{%- endcomment -%}
    ...etc -%}

(Exact Liquid syntax is in corrected-snippets.md — don't reinvent it, just extend the existing condition.)

3

Deploy all 4 stores

Same as Playbook A step 4. Replace the section-43 block on each store's layout/theme.liquid.

4

Verify via cache-bypass fetch

Expect 5 tags on /pages/testimonials on each of the 4 stores. If you get 2 tags, the allowlist didn't catch — check the handle spelling exactly matches what Shopify stores as page.handle (lowercase, hyphenated).

Playbook C Renaming an existing product slug on one store

You've changed a product's URL on (for example) the FR store. Shopify will 301 the old URL to the new one, but the hreflang snippet's when clause still lists the old handle. Update it — keep the old handle as an alias for redirect safety.

1

Confirm the new handle in Shopify admin

Admin → Products → [product] → scroll to the URL field at the bottom of the page. Copy the exact handle.

2

Update the JSON

Open F:/Claude Root/config/hreflang_snippets.json. In the affected product's entry:

  • Add the new handle to the handles array (keep the old one as a safety alias)
  • Update urls.[locale] to the new full URL
3

Update the store's snippet

Open corrected-snippets.md. In the affected store's snippet, find the {% when %} clause for this product. Add the new handle to the list. Update the locale's <link> href inside the clause.

4

Deploy and verify that one store

Paste the updated snippet into the affected store only. Verify via cache-bypass fetch on both the old URL (should 301 and still show 5 tags) and the new URL (should show 5 tags directly).

Troubleshooting — verification failed?

Product shows 2 tags instead of 5
{% when %} didn't catch. 99% of the time: handle typo, or you missed a store's handle. Re-check the exact product.handle value from /products.json. Paste it verbatim.
Shared page shows 2 tags instead of 5
Allowlist condition didn't catch. Handle must exactly match page.handle (lowercase, hyphenated, no /pages/ prefix). Check the path spelling in the elsif chain.
5 tags emitted but one href is wrong
URL typo in the <link> href. Check the config/hreflang_snippets.json urls map first (source of truth), then the Liquid.
Browser view-source shows old tags, cache-bypass fetch shows new tags
Normal. Shopify edge cache + browser cache can lag 5–15 minutes. Trust the cache-bypass fetch. If it looks right, it is right.
FR product emits https://www.brainzyme.fr/... (with www)
You typed www in the fr-FR href. FR canonical is non-www — strip the www. Check the JSON too, not just the Liquid.
Something is very wrong and I want to undo
The original block is in each theme's git history inside Shopify. Theme editor → Older versions → restore layout/theme.liquid. Or paste the v2 snippet from corrected-snippets.md to reset to the last-known-good state.
Can't be bothered? Ask Claude.

Any future Claude session can run the full SOP end-to-end. The relevant memory files (reference_hreflang_snippets.md, feedback_shopify_product_handle_per_store.md) are indexed in Pinecone and surface automatically on any hreflang-related query. Say "add Focus Plus to hreflang" and it'll walk through all 4 steps.

05The v2 FR long-handle patch

Post-deploy verification caught a live bug: FR ELITE and FR PRO pages emitted only 2 tags instead of 5. Root cause was an invisible trap in Shopify's multi-store behaviour.

The trap

product.handle on each Shopify store returns that store's actual handle, not a canonical cross-store identifier. UK/US/DE use short handles (brainzyme-elite, brainzyme-elite-3-in-1-formula, brainzyme-professional). FR uses long localised slugs like:

brainzyme-elite-stimulation-agreable-concentration-memoire-
un-complement-nutritionnel-d-optimisation-de-performance-
cognitive-nootropique-naturel

The v1 snippet's {% case product.handle %} only listed UK/US handles. FR products fell through every when branch, hit the self-only fallback, and emitted 2 tags.

The fix

v2 adds all three FR long handles to ELITE / PRO / ORIGINAL when clauses (COMBO already had it). Each product's full handle set:

ProductUKUSDEFR long handle
ELITEbrainzyme-elitebrainzyme-elite-3-in-1-formulabrainzyme-elitebrainzyme-elite-stimulation-agreable-...
PRObrainzyme-professional-stronger-formulabrainzyme-professional-stronger-formulabrainzyme-professionalbrainzyme-professionnel-formule-renforcee-...
ORIGINALbrainzyme-original-milder-formulabrainzyme-original-milder-formulabrainzyme-originalbrainzyme-original-formule-douce-...
COMBObrainzyme-combo-set-1x-elite-...brainzyme-combo-set-1x-elite-...brainzyme-combonouveau-brainzyme-pack-combo-x1-elite-...
Lesson banked as a permanent rule

In any multi-store Liquid that branches on product.handle, every store's actual handle must be enumerated. Don't assume canonical aliasing. Before shipping, query each store's /products.json?limit=250 and inspect the real handle values.

Memory file: feedback_shopify_product_handle_per_store.md.

06Should we unify slugs across stores?

Natural follow-up question: if FR's long handles are the trap, why not just make all four stores use identical slugs? The answer: technically yes, practically no.

What unified slugs would buy us

What it would cost

Recommendation: keep localised slugs

The current snippet is the right trade-off. Self-healing for the 99% case (new blog/collection content) and a small, predictable code change for the rare product or mirrored-page addition.

07Banked artefacts

Everything needed to understand, maintain, or re-deploy the fix is banked. Files below are absolute paths on the working machine.

Pipeline / deploy

F:/Claude Root/hreflang-audit/corrected-snippets.md
Paste-ready v2 Liquid blocks — 4 store-specific snippets.
F:/Claude Root/config/hreflang_snippets.json
Structured data: product map, mirrored-pages allowlist, per-store metadata, deploy status.
F:/Claude Root/config/store_urls.json
2,939-URL sitemap harvest across all 4 stores — drove the allowlist decisions.
F:/Claude Root/hreflang-audit/diagnosis-report.md
Full pre-fix diagnosis: what was broken, why, and evidence per store.

Memory / session record

memory/reference_hreflang_snippets.md
Canonical reference: tier structure, per-product handle table, FR canonical rule, post-deploy checklist.
memory/feedback_shopify_product_handle_per_store.md
The FR long-handle lesson as a permanent rule. Applies to any multi-store Liquid.
memory/working-context/session-35-hreflang-audit.md
Session record — ENDED. Summary + pointers.
F:/Claude Root/CHANGELOG.md
Dated log entry (14 April 2026) with the full impact summary.

08ClickUp tasks

Assignee: Calum. Implementer: Lu. Parent task has description + audit trail; subtasks carry the inline paste-ready Liquid per store.

All 5 tasks resolved. FR subtask carries the v2 patch notice post — original v1 code is superseded.