Your multilingual site is probably only indexed in one language
Switching language with JavaScript on a single URL is the most common way to make six translations invisible to search engines. Here is what actually gets indexed, and how to structure a static site so it does not happen.
A pattern we see often: a company translates its site into six languages, ships a language switcher, and sees no traffic from any of the new markets. The translations are good. Nobody finds them.
The cause is almost always the same, and it is architectural rather than editorial.
Search engines index URLs, not application state
If your language switcher swaps text on the page and remembers the choice in localStorage or a cookie, then every language shares one URL.
A crawler requests that URL, gets whatever language is baked into the HTML, and indexes that. Your other six translations do not exist as far as search is concerned. There is no second URL to rank, no separate title, no separate description.
This is not a crawler limitation to work around. A URL is the unit of indexing. One URL means one indexed document.
The test is simple: can you send someone a link that opens your page in Japanese? If not, Google cannot index a Japanese version either.
What client-side switching is fine for
Worth being fair to the pattern, because it is not always wrong. Client-side switching is perfectly reasonable when the page is not something people find by searching its text:
- App interfaces behind a login
- Dashboards and tools
- A small marketing site whose traffic comes from ads, referrals or word of mouth
It becomes a problem the moment you want organic search traffic in those languages. Content marketing, documentation, and blogs are exactly that case.
Give each language a real URL
Three common structures, all acceptable to search engines:
| Structure | Example | Notes |
|---|---|---|
| Subdirectory | example.com/ja/page | Simplest. Inherits the domain's authority. Usually the right default. |
| Subdomain | ja.example.com/page | Workable, more DNS and certificate management. |
| Separate domain | example.jp/page | Strongest local signal, most expensive. Each domain builds authority alone. |
For most teams, subdirectories. You get one domain accumulating authority and no infrastructure work.
Then tell search engines the pages are related
Separate URLs alone create a new risk: seven pages saying the same thing can look like duplication. hreflang is how you say "these are the same page in different languages, show the right one".
On every version of a page, list every version, including itself:
<link rel="alternate" hreflang="en" href="https://example.com/en/page">
<link rel="alternate" hreflang="vi" href="https://example.com/vi/page">
<link rel="alternate" hreflang="ja" href="https://example.com/ja/page">
<link rel="alternate" hreflang="x-default" href="https://example.com/en/page">
Plus a self-referencing canonical on each:
<link rel="canonical" href="https://example.com/ja/page">
The five mistakes that break it
- Missing return links.
hreflangmust be reciprocal. If the English page points to Japanese but Japanese does not point back, the pairing is ignored. This is the most common failure by a wide margin. - Canonical pointing at another language. A Japanese page whose canonical points at the English URL is instructing search engines to drop it. Canonical must point at itself.
- Language and region confused.
hreflang="ja"is the Japanese language.hreflang="ja-JP"is Japanese in Japan specifically. Use the bare language code unless you genuinely have region-specific variants; needless narrowing loses you readers elsewhere. - Wrong codes. The region subtag is a country code, not a language one. British English is
en-GB, noten-UK. For Chinese, script matters more than country:zh-Hansandzh-Hant. - Relative URLs.
hreflangand canonical want absolute URLs with protocol and domain.
Translate the slugs too
An easy win that gets skipped: the words in the URL are a relevance signal in that language. If your Vietnamese page lives at /vi/how-to-look-up-a-character, the URL is contributing nothing in Vietnamese.
Translate the slug and keep a stable internal identifier to pair the versions. That is exactly how we structured this blog: each post has an id that never changes and a per-language slug that does.
Do not auto-redirect by IP
Tempting, and it causes real damage. If you detect a visitor's country and force a redirect:
- Crawlers mostly request from one country and can end up seeing only one version.
- You override people who deliberately chose otherwise. Plenty of people in Japan want to read English.
Suggest, do not force. Offer a banner, keep the switcher visible, and let the URL decide the content.
Static sites make this easy
None of this needs a CMS or a server. Generate one HTML file per language per page at build time, with the correct tags baked in, and upload the folder. The output is plain static files; the intelligence is in the generator you run before deploying.
That is what we do for this blog: content lives in one file per post per language, a small script emits the HTML with hreflang, canonical, JSON-LD and per-language sitemap entries, and the site itself stays a folder of static files with no backend at all.
A checklist
- Every language has its own URL that you can send to someone
- Every page lists every language in
hreflang, including itself, reciprocally - An
x-defaultexists for visitors you have no match for - Canonical on each page points at itself
- Slugs are translated, with a stable id pairing them internally
- The sitemap lists every language URL with its alternates
html langmatches the actual content- No IP-based forced redirects