











When building a multilingual static blog with Next.js, there are two fundamentally different approaches to internationalisation. Choosing between them involves trade-offs across SEO, developer experience, content strategy, and hosting complexity.
The simplest approach stores language preference in client state and resolves translations after hydration:
For page content that has locale variants, all versions are server-rendered into the HTML and a thin client wrapper toggles visibility:
This works and is zero-config — no routing changes needed. But it has real limitations.
localStorage reads, so it always indexes the default locale's content.display:none content is risky. Google may partially index hidden locale variants, or ignore them entirely.hreflang signals. Search engines can't discover alternate language versions of a page.<html lang> not updated. Accessibility and search engine tooling rely on this attribute.The proper solution gives each language variant its own URL:
With Next.js App Router, this means moving all routes under an app/[locale]/ segment and using generateStaticParams to pre-render each route for each locale at build time.
Since Amytis uses output: "export", this is pure Static Site Generation — the HTML is fully pre-built per locale. Crawlers get complete HTML with no JavaScript required, and each locale is independently indexable.
Most sites want the default locale to have a clean URL (/about, not /en/about). With a runtime server, middleware handles this transparently. With static export, you need your hosting layer to do the rewrite.
Drop a _redirects file in public/:
The 200 status is a silent rewrite — the user sees /about in the URL bar, but the server serves out/en/about/index.html.
GitHub Pages has no native rewrite support. The simplest workaround is a root index.html with a JS language detector:
Or just accept the /en/ prefix for all locales.
URL-based routing changes more than just URLs — it changes how you think about content.
For a personal digital garden, most content is written in one language and won't be fully translated. The realistic scope is:
| Content Type | Translated? | Approach |
|---|---|---|
| Static pages (about, privacy) | Yes | Optional .zh.mdx variant files |
| Posts | Rarely | Fallback to original + notice |
| Series descriptions | Maybe | Optional index.zh.mdx |
| Books | Unlikely | Fallback |
| Flows (daily notes) | No | UI-only i18n, no locale in URL |
The .{locale}.mdx convention — already used for static pages in Amytis — extends naturally to posts and series:
At build time, generateStaticParams generates /en/my-post and /zh/my-post. If the .zh.mdx file doesn't exist, the Chinese URL falls back to the English content with a small "not available in this language" banner.
| Aspect | Client-Side Toggle | URL-Based Routing |
|---|---|---|
| SEO | Crawlers see default lang only | Each locale fully indexed |
| Default locale prefix | No prefix | CDN rewrite needed |
| Build size | Same | ~2× |
| Refactor scope | None | Large |
| Content strategy | .zh.mdx for pages only | .zh.mdx for all content types |
For a personal blog where SEO in a second language isn't critical, the client-side toggle approach is pragmatic and gets the job done. UI strings translate, page content can optionally have locale variants, and there's no hosting complexity.
For a project where bilingual SEO matters — where you genuinely want Chinese content indexed under Chinese URLs — URL-based routing is the right architecture. The CDN configuration is a one-time setup, and the .{locale}.mdx convention for content is already half-implemented.
The good news: both approaches share the same content file convention. Migrating from client-side toggle to URL-based routing is a routing and build change, not a content restructure.
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。