Handling Currency, Language, and SEO Wreckage in Global Stores
Currency-Based URL Variants and Crawling Madness
If you’re running a Shopify or WooCommerce store and thought adding multi-currency support was as simple as dropping in a plugin or enabling a dropdown… congrats, welcome to your new crawling nightmare. Many setups create separate URL parameters or even subdirectories for currencies, like ?currency=USD
or /eu/
. Only problem? That introduces a hundred variations of the same product page.
Googlebot doesn’t love that. Especially not if you forgot to set your canonical tags properly—or worse, if your system injects the canonical *after* the page renders due to React hydration or a rogue theme setting. Been there, unwittingly duplicated an entire product catalog into six currencies before I caught it in GSC’s coverage report.
“Our server logs showed Googlebot requesting the same URL 18 times with different currency parameters, none canonicalized. Bounce rate was a mess.”
Rule of thumb: if your pricing is dynamic based on IP or browser locale, keep the URL static. Use client-side tweaks to reflect pricing, or if you NEED to vary URLs, use proper hreflang
and rel=canonical
headers server-side—not just meta tags injected into the DOM.
How Hreflang Really Behaves in Multi-Language Ecomm
Okay, so hreflang
values sound good on paper. Tell Google the English page for Canada is one thing, the French page is another. Simple, right? Nope. The minute you deploy per-locale content with minor wording differences and lazy translations (looking at you, hand-translated product info blocks), you run into wild ranking mismatches.
I had a French page for “chaussures de course” outrank the Canadian English equivalent, despite it not having a single backlink and getting barely any traffic. It turns out: Google silently ignores hreflang
if it thinks the page doesn’t serve the right intent linguistically or uses near-duplicate structures. No warning. No GSC notice. The only indication was when I ran a manual “site:” query and the wrong language page kept showing up.
Undocumented quirk
If a locale page is 80% structurally identical (e.g., same images, layout, button text), Google deprioritizes hreflang
affinity. You won’t find this in any docs. But if the meta-description and h1 are too similar across variants, no amount of hreflang
will save your CTR.
Google Merchant Feeds + Region Redirects = Mess
At one horrible point, a team I was helping had auto-redirects based on GeoIP. Innocuous at first—you visit from the UK, it boots you to the /uk/
store. Classic behaviour. But when we uploaded the product feed to Google Merchant Center, and those links had zero redirect-exclusions for bots? Entire feed got flagged for “destination URL mismatch”.
Turns out GMCenter uses US proxies to check your links. If your server boots every non-EU IP to a 301 with cookies involved, Google thinks your link doesn’t land on a real product page. Boom—feed suspended even though the pages are technically online. Getting this cleared took three appeals and finally a support guy said (paraphrasing):
“We recommend disabling geographic redirects on any store meant for feed-based shopping campaigns.”
No mention of this in their docs. I ended up having to whitelist all Google IPs in our Cloudflare config to disable redirects—which needed a nasty little list extracted from their ASN block via whois
and a coffee-fueled rage script.
When Structured Data Betrays Multi-Currency Product Pages
This one slipped past QA because nobody was looking at the rendered schema in real time. We had schema generated server-side with JSON-LD, reflecting the default currency (let’s say USD). But on currency switch (say, to GBP), the frontend dynamically showed different prices… while the schema still declared the price in USD.
Google Search Console didn’t throw errors. Instead, it silently cached the structured data as USD—even for UK users. We didn’t notice until a UK searcher called in confused by the price discrepancy, which was like a 20% difference. Easily lost conversions we never got back.
Fix was dumb-simple in hindsight: detect the displayed currency and update the schema block dynamically on currency switch. Yes, it’s brittle. Yes, it meant coupling our React state to a template rewrite function. But better that than mispriced snippets.
{
"@context": "http://schema.org",
"@type": "Product",
"name": "Super Running Shoes",
"offers": {
"@type": "Offer",
"priceCurrency": "GBP",
"price": "79.99",
"availability": "http://schema.org/InStock"
}
}
Above was generated from a hydrated state object. Ugly, but accurate.
Alternate Locales and Canonical Trapdoors
If you serve product pages at both example.com/en/
and example.com/fr/
, be very careful where your canonical points. The logic I ran into: the French page had <link rel="canonical" href="https://example.com/en/product">
, defaulting everything back to the English version.
This destroyed the indexing of the French version. In Search Console, the fr version became “Duplicate, submitted URL not selected as canonical,” even though users could land on it. Only after removing the override and letting Google decide did it start indexing both.
Aha moment
Turned out the CMS template was injecting a hard-coded canonical because “English is our primary market.” Canonical isn’t a preference—it’s a directive. Don’t use it for site-wide biasing, especially if your alt languages have unique content.
Hard-Learned Tips for Multi-Language Product Descriptions
Eventually, I just made myself a checklist after breaking too many things on translation rollouts. These saved me more than once:
- Never run auto-translation on brand names or model numbers. Ever.
- Check plural logic—some product filters break when the translated string changes in casing (e.g., “Sneakers” → “espadrilles”).
- Use absolute paths, not locale-relative, for related products links (
/fr/product/123
not../product/123
). - Verify sitemap XML files separately per locale. Don’t rely on the master sitemap index if your CMS merges everything dynamically.
- Meta titles need to fit localized SERP pixel widths—German and Finnish titles truncate way faster.
- Do NOT localize schema type names. I saw someone change
Product
toProdukt
and wondered why validation exploded.
How Shopify Markets Quietly Overwrites Your Meta Tags
This one made me audibly complain at my screen. Shopify Markets lets you set per-country prices, URLs, and language content. Great. But somewhere around a few store rollouts ago, we realized that Shopify was injecting a new meta robots=noindex
on variants it considered geo clones with “too little content variance.”
No notification. No GSC alert. I only caught it when a Japanese variant dropped out of the index randomly. Logged the HTTP response, and yep—noindex snuck in. Digging through Shopify support wasn’t helpful, though one agent finally hinted at a system that flags “duplicate or unsupported market content” based on template parity and product count.
Only fix: rework the store descriptions or add unique media per locale. So I slapped a few localized testimonials on each one (cheating, yes) and magically the noindex disappeared on recrawl.
CDN-Level Caching That Ignores Accept-Language Headers
If you’re caching at the CDN level (Cloudflare, Akamai, etc.) and don’t vary caching by Accept-Language
or key by cookie/session, then language-specific pages will serve the language of the first cache-pop. This is maddening if your template renders based on browser locale and you thought you were being clever with SSR.
I had a Vietnamese visitor suddenly see the German version. Turns out the CDN had cached the first request for that URL (from a German crawler), and now every user got that version unless something broke the cache with a unique variation. Best part? The live preview URL always looked correct because it bypassed CDN edge logic.
If you’re seeing weird mismatches between preview and production in multi-language mode: flush your CDN, test with a fresh incognito and curl the response headers. Look for a x-cache
hit and what content-language headers you’re pushing out. It gets real dubious real fast once cache logic starts leaking languages.