← Back to blog

The i18n + SEO Cleanup Chronicles: Canonical Chaos, hreflang Therapy, and Other Adventures

A long-form dev log on fixing multilingual routing and SEO consistency in CryptoBacktest, based on real commits across language toggles, canonical tags, sitemap, and robots.

by Jay5 min readCRYPTOBACKTEST B.LOG

CryptoBacktest blog page

If you ever hear someone say:

"International SEO is easy, just add hreflang."

Please smile politely, offer them water, and slowly change the topic.

Because this saga was not one change.
It was a chain reaction across links, canonicals, sitemap entries, language toggles, and pages that all looked okay until you opened source and cried a little.

πŸ“‹ Why This Cleanup Happened

By mid-February, we had enough English/Korean pages that small mistakes multiplied:

  • wrong counterpart links
  • incorrect canonical paths
  • missing or mismatched alternates
  • language toggle inconsistencies

Everything still rendered. That was the dangerous part.

Broken i18n SEO rarely crashes your app. It just silently undercuts discoverability.

πŸ”§ Commits That Defined the Cleanup

Core sequence:

  • a297d74 (2026-02-15): fix language toggle redirect mappings
  • c2a31c1 (2026-02-16): canonical + hreflang correction sweep
  • f861324 (2026-02-16): sitemap/robots overhaul
  • 2334e8f (2026-02-20): EN toggle style fix on Korean landing page

Yes, even button style made it into the SEO cleanup narrative. Because if users can't confidently switch language, your perfect hreflang setup still loses practical value.

πŸ”‘ Canonical + hreflang: A Real Diff Example

(For a comprehensive reference on canonical, hreflang, and all other SEO tags, see The Complete SEO Guide. This section focuses on the specific bugs we found in CryptoBacktest.)

From c2a31c1, English pages gained explicit alternates:

<link rel="canonical" href="https://cryptobacktest.com/app.html">
<link rel="alternate" hreflang="en" href="https://cryptobacktest.com/app.html">
<link rel="alternate" hreflang="ko" href="https://cryptobacktest.com/ko/app.html">
<link rel="alternate" hreflang="x-default" href="https://cryptobacktest.com/app.html">

And Korean pages were corrected from bad paths like:

<link rel="canonical" href="https://cryptobacktest.com/ko/ko/app.html">

to:

<link rel="canonical" href="https://cryptobacktest.com/ko/app.html">

That one duplicated /ko/ko/ was small in text, large in consequences.

πŸ› How We Found the /ko/ko/ Bug

Discovery wasn't glamorous. It started in Google Search Console, where we noticed a handful of Korean pages returning 404s in the crawl report. At first, the suspicion was that Google's bot was confused. Then we manually checked the sitemap and found the culprit: somewhere in the route generation logic, Korean pages were being assigned /ko/ twice instead of once.

The code path that built language-prefixed routes had a conditional that wasn't resetting the prefix properly after the first iteration. By the time we traced it, several pages had already been indexed with the wrong canonical, and we had to submit a removal request alongside the fix.

It's a reminder that i18n bugs are often invisible until they break in production. Static site generators don't always scream at you mid-build if a route prefix gets doubled. They just... generate it.

🌐 Language Toggle Consistency Is Part of i18n Quality

From 2334e8f (ko/index.html), we fixed the EN toggle class/styling mismatch:

<a href="../index.html" class="btn-nav lang-toggle" style="background:rgba(255,255,255,0.1);border:1px solid rgba(255,255,255,0.2);color:var(--text-primary);padding:6px 14px;" title="English / Korean">🌐 EN</a>

This looked cosmetic, but it fixed a real trust issue. When language controls look broken, users assume language behavior is broken too.

And honestly? They are often right.

πŸ“œ Robots + Sitemap: Not Glamorous, Very Important

f861324 added robots.txt and expanded sitemap.xml metadata quality.

Snippet from robots.txt:

User-agent: *
Allow: /
Sitemap: https://cryptobacktest.com/sitemap.xml

And the sitemap moved from bare URL lists to richer entries:

<lastmod>2026-02-16</lastmod>
<changefreq>weekly</changefreq>
<priority>0.9</priority>

Is this exciting? No. Does this matter? Absolutely.

SEO quality is usually a stack of "boring but correct" decisions.

πŸ–ΌοΈ Screenshot of the Core Surface

CryptoBacktest home page

The homepage is where language identity starts:

  • nav labels
  • CTA clarity
  • route expectations
  • trust signals

If i18n is shaky here, everything downstream costs more.

πŸ’­ Tangent: Why This Felt Hard as a Non-Developer

As a non-traditional builder, I expected hard things to be:

  • strategy math
  • indicator tuning
  • simulation complexity

Turns out, one of the hardest things is making dozens of static pages behave like one coherent multilingual product.

No one claps for canonical tags in demos. But they absolutely notice when your language routing feels broken.

The real friction point isn't the conceptβ€”it's the tooling gap. Most static site generators (and frameworks built on top of them) assume you'll handle i18n at build time, which is great for performance. But they rarely give you helpers for verifying that alternates are reciprocal, that canonical paths match actual routes, or that language toggles always point to valid pages. You end up writing custom validation scripts, or worse, doing it manually. A dynamic site would catch these errors in middleware. A static site requires discipline, checklists, and spot checks.

Practical Checklist We Ended Up Using

If you're doing a similar cleanup, this saved us:

  1. every page must have canonical + en/ko/x-default set
  2. language toggle must map to exact counterpart route
  3. sitemap alternates must mirror page-level alternates
  4. robots must include sitemap
  5. style consistency for language switch controls

Simple list. Massive reduction in subtle breakage.

🎯 Final Take

Internationalization is not a single feature. It's a reliability discipline.

You do not "finish i18n" once. You maintain it every time routing, content, or UI changes.

And yes, sometimes that means spending an evening debugging one path segment like /ko/ko/ and questioning your life choices.

That is still better than leaving it broken and wondering why search traffic feels weird three weeks later.


For VORA's approach to the same bilingual challenge β€” using paired HTML pages instead of route prefixes β€” see How We Made VORA Bilingual.

Written by

Jay

Licensed Pharmacist Β· Senior Researcher

Building production-grade AI tools across medicine, finance, and productivity β€” without a CS degree. Domain expertise first, code second.

About the author β†’
ShareX / TwitterLinkedIn