惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Martin Fowler
Martin Fowler
Engineering at Meta
Engineering at Meta
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
阮一峰的网络日志
阮一峰的网络日志
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
量子位
Jina AI
Jina AI
Microsoft Azure Blog
Microsoft Azure Blog
博客园_首页
L
LangChain Blog
A
About on SuperTechFans
人人都是产品经理
人人都是产品经理
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
美团技术团队
博客园 - 三生石上(FineUI控件)
N
Netflix TechBlog - Medium
D
DataBreaches.Net
P
Proofpoint News Feed
小众软件
小众软件
Vercel News
Vercel News
T
The Blog of Author Tim Ferriss
WordPress大学
WordPress大学
雷峰网
雷峰网
G
Google Developers Blog

tanscp

科学驾驭你的身体电量 (Body Battery) - tanscp Code Block Features Showcase 通俗易懂的解释什么是Agent - tanscp Multimedia Showcase: Video, Podcasts & Embeds Syntax Highlighting Mathematical Notation Visuals and Diagrams System Architecture 101 Syntax Highlighting Showcase 中文测试文章 Part 2: Routing Mastery Part 1: Getting Started with Next.js 15 多语言测试并且要测试以下超长的标题看如何处理,如果太长的话要考虑方案: Amytis Digital Garden The Kitchen Sink: Comprehensive Feature Test Mac 菜单栏武装计划:10 款免费开源的宝藏 App 推荐 Markdown Features Test Nested Image Test Legacy Markdown Support Asynchronous JavaScript QuickSort: An Algorithmic Deep Dive README Index Post Deeper Notes 在一个被屏幕和算法全面包围的时代,我们正逐渐失去对“注意力”的主权 - tanscp AI 是如何识别一只猫的?——图解神经网络的秘密 - tanscp 什么是真正的自我?它真的存在吗? - tanscp AI洞察从你的笔记中读出了什么? - tanscp 我尝试空腹有氧一个月后 - tanscp ISSUE 026 | 数独事件 - tanscp ISSUE 025|你喜欢哪个版本的自己? - tanscp ISSUE 024|认知失调 - tanscp
i18n in a Static Next.js Blog: Client-Side Toggle vs URL-...
John Hu · 2026-02-20 · via tanscp

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 Client-Side Toggle Approach

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.

SEO Problems

  • Crawlers always see the default language. Googlebot doesn't execute 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.
  • No 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.

URL-Based Locale Routing

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.

The Default Locale Prefix Problem

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.

Netlify / Cloudflare Pages

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.

Vercel

Nginx

GitHub Pages

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.


Content Strategy

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 TypeTranslated?Approach
Static pages (about, privacy)YesOptional .zh.mdx variant files
PostsRarelyFallback to original + notice
Series descriptionsMaybeOptional index.zh.mdx
BooksUnlikelyFallback
Flows (daily notes)NoUI-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.


Trade-offs at a Glance

AspectClient-Side ToggleURL-Based Routing
SEOCrawlers see default lang onlyEach locale fully indexed
Default locale prefixNo prefixCDN rewrite needed
Build sizeSame~2×
Refactor scopeNoneLarge
Content strategy.zh.mdx for pages only.zh.mdx for all content types

Conclusion

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.