I spent the last few weeks building a multilingual information and tools site for an upcoming MMORPG.
It started as a fairly simple idea: publish useful guides and an interactive map.
It eventually became a system containing:
3 languages
10 map definitions
3,792 published map points
30 map-and-language runtime bundles
9 separate sitemaps
An official-item database
Crafting and material calculators
A class finder
A content-verification pipeline
A domain and brand migration
A Search Console-driven SEO experiment
The product is already live, so this is not a “day one” build-in-public announcement.
It is a retrospective on the decisions that became unexpectedly difficult.
I initially treated the map as a frontend feature.
Render some tiles, add markers, build filters, and ship it.
The difficult part was not displaying a coordinate. It was deciding what that coordinate represented and how it could be corrected later without silently changing the live dataset.
I ended up separating map data into three layers.
This preserves the original record, source location, acquisition context, and original value.
A missing value stays missing. It does not automatically become 0, false, or an empty translated string.
“Unknown” and “confirmed false” are different claims.
This layer assigns stable identities and aligns:
Map IDs
Marker IDs
Coordinates
Categories
Factions
Translation keys
Source metadata
A marker keeps the same identity when its English name changes or its Korean translation is corrected.
This layer decides which data is ready for public use and which pages are complete enough to appear in search.
Some maps have a usable base image but incomplete point data. They can still exist inside the application without receiving an indexable landing page.
That distinction became important throughout the project:
A page being technically available does not mean it is ready to become a search result.
The site supports Traditional Chinese, English, and Korean across 10 map definitions.
Instead of making every visitor download one large mutable JSON file, the release pipeline generates one bundle for each map-and-language combination.
The browser first requests a small manifest and then downloads only the bundle needed for the active map and language.
Tiles, marker icons, and data bundles use content-addressed version keys and immutable caching.
A small stable pointer determines which release is currently active.
That also changed how rollback works.
I do not delete or overwrite the previous map release. Rolling back means moving the stable pointer to an older verified version.
It is probably more release infrastructure than a niche gaming site normally needs, but it gives me traceable corrections and deterministic releases.
An interactive map wants gestures, filters, client-side state, and large datasets.
A search landing page needs stable URLs, readable HTML, clear descriptions, and useful content before JavaScript runs.
I stopped trying to make one layer solve both problems.
Before the interactive map loads, the server-rendered page already contains:
The region and map name
Faction information
The number of published points
Available marker categories
A static map preview
Data update dates
Related maps and guides
Canonical and alternate-language URLs
The MapLibre application loads later when it approaches the viewport or when the visitor explicitly enables it.
Temporary searches, filters, selected markers, and “already found” states remain in URL fragments or local storage. They do not create thousands of crawlable URL combinations.
My working rule became:
Stable editorial state belongs in an indexable URL. Temporary interaction state belongs in the application.
The site has explicit paths for:
/zh-hant/
/en/
/ko/
The difficult part was making every system agree that these URLs were localized versions of related pages while still being independently indexable.
I split the sitemap structure in two dimensions.
By language:
Traditional Chinese
English
Korean
By page type:
General pages and tools
Editorial content
Maps
That produces nine child sitemaps behind one sitemap index.
Every indexable page is checked for:
A self-referencing canonical
Reciprocal hreflang alternatives
An x-default URL
Localized title and description
Consistent indexability
Valid structured data
Membership in exactly one sitemap class
The “exactly one sitemap” rule prevented new tools from accidentally appearing in two sitemap groups—or in none.
I also learned that translating the English metadata and applying the same character limits does not work well.
Different languages need different titles, terminology, and sometimes different descriptions of the same page.
The item database can generate routes for a large number of official item IDs.
But an ID, name, icon, and a few properties do not automatically create a valuable search result.
Most ordinary item-detail pages remain outside the sitemap until they have enough context to answer a real question.
The same applies to maps and tools:
Incomplete maps remain noindex.
Temporary filters remain noindex.
Calculator-share states remain noindex.
A tool joins the sitemap only after its registry status becomes live.
Editorially developed pages can be promoted later without changing their underlying identity.
This reduced the total number of indexable URLs, but it also stopped me from confusing “programmatically generated” with “programmatically valuable.”
The crafting calculator combines two different kinds of data.
Official data controls item identity:
Item ID
Localized name
Icon
Category
Published properties
A dated community snapshot controls recipe relationships:
Required materials
Quantities
Craftable intermediate materials
Recipe relationships
I did not want an official item name to make the entire recipe look official.
The interface therefore keeps the source boundary visible.
It also avoids a few misleading assumptions:
An unknown price is not zero.
A missing recipe is not proof that an item cannot be crafted.
An official item ID does not make a community recipe official.
A calculated result is only as current as its recipe snapshot and user inputs.
This same separation now influences the editorial content. Official facts, community observations, and my own assessments receive different labels.
The project started under a temporary origin and an earlier product identity before moving to the final AION2 KINA domain.
The migration required more than changing a configuration value.
I had to coordinate:
Permanent redirects
Canonical URLs
Internal links
Structured data
Sitemap hosts
Search Console properties
URL inspection
Brand metadata
Analytics
Old and new URL monitoring
I am tracking indexing as a process rather than declaring success because the homepage appeared in search.
For representative pages, I monitor:
Last crawl date
Google-selected canonical
Sitemap discovery
Indexed versus crawled status
Impressions by domain
Old URLs still receiving traffic
Recovery of branded and non-branded queries
One useful lesson was that site: search counts are not precise enough to evaluate a migration. Search Console’s indexing and canonical data are much more useful.
After reviewing Search Console and metadata-audit feedback, I identified several improvements that could be applied across the site.
The problem was that changing titles, descriptions, structured data, content summaries, and performance simultaneously would make the result impossible to interpret.
I stopped the broad rollout and narrowed the experiment to one article about a third-party combat-recording tool.
The page has a clear query space and a clear trust problem: readers need feature and installation information, but they also need to understand administrator permissions, upload behavior, reviewed version, and the limits of independent safety verification.
Instead of asking “Can I increase CTR?”, the experiment became:
Can more precise intent and risk language improve qualified search engagement without turning the snippet into marketing copy?
I am monitoring impressions, CTR, average position, irrelevant query growth, engaged visits, and whether Google rewrites the title or description.
The important change was defining a falsifiable reason before changing the rest of the site.
If I started again, I would define these boundaries earlier:
Source data versus normalized data
Available pages versus indexable pages
Official facts versus community observations
Stable URLs versus temporary application state
A verified release versus the currently active release
Most of the difficult work came from discovering those distinctions after the UI already existed.
Explore the live build
Everything below is already available in English, Traditional Chinese, and Korean.
Interactive map — Search 3,792 published map points with localized filters and crawlable map summaries.
Class finder — Answer six playstyle questions and receive three class matches.
Crafting and material calculator — Calculate direct materials, base materials, inventory shortages, and custom-price costs.
Daily and weekly checklist — Create custom routines stored locally on your device.
Official-item database — Browse localized equipment, materials, consumables, and stable item details.
AION2 code center — Check active and expired coupons, official dates, rewards, and redemption instructions.
Source-led guides — Read version-aware guides with official facts and unverified information clearly separated.
Classes and comparisons — Compare roles, weapons, learning difficulty, and practice frameworks without unsupported tier rankings.
Official news tracking — Follow verified announcements, events, regional releases, and system updates.
The complete project is AION2 KINA.
Which part should I unpack next: the map pipeline, multilingual SEO, domain migration, or data provenance?
The part that stood out wasn't the number of features—it was the boundaries you introduced as the project grew.
Separating concepts like "available" vs. "indexable" and "source data" vs. "published data" usually makes systems easier to evolve because changes stop leaking across unrelated parts of the product.