使用您最喜欢的AI助手总结文档,并引用此页面和AI提供商
如果您有改善此文档的想法,请随时通过在GitHub上提交拉取请求来贡献。
文档的 GitHub 链接复制文档 Markdown 到剪贴板
next-intl VS Intlayer | Next.js 国际化 (i18n) 基准测试

next-intl 是 Next.js 最流行的 i18n 库。Intlayer 是一个基于编译器、组件作用域的替代方案。两者都可以本地化 App Router 应用程序。问题是应用程序构建后每个库的成本是多少。
本文不是教程。这是一个由 Benchmark Bloom 的数据支持的比较,Benchmark Bloom 是一个开源基准测试套件,它使用每个库构建相同的应用程序,并测量浏览器实际下载和执行的内容。
tl;dr: 在相同的 Next.js 应用上,next-intl在每个页面上增加 +12.6 KB gzip 的 JavaScript,而 Intlayer 仅增加 +0.3 KB。无需额外工作,next-intl在每个页面上附带 ~90% 的外语页面字符串。要通过next-intl达到 0% 泄漏,需要命名空间作用域和按页面的pick(messages, [...])。Intlayer 默认达到 0%,因为其编译器按组件作用内容。如果你想要 Intlayer 输出的next-intlAPI,@intlayer/next-intl适配器测得每个页面 147.5 KB 对比原始的 153.6 KB。
简而言之
- next-intl - 轻量级、文档齐全、支持 ICU 消息格式、App Router 第一类支持(含 middleware)、formatters 和 navigation 助手。内容存储在集中式 JSON catalogs 中;性能优化(namespaces、per-page message picking、lazy loading)需要你自己实现。
- Intlayer - 以组件为中心的内容模型。
.content.ts字典与服务的组件并置,build-time compiler 对每个组件和每个 locale 进行 tree-shake 和 lazy-load,从你的内容生成严格的 TypeScript 类型,缺失的翻译在 build 时会失败。提供 middleware、SEO 助手、Visual Editor / CMS 和 AI 辅助翻译。
在弹窗中打开表格以清晰地查看所有数据
徽章自动更新。快照会随时间变化。
并排功能比较
在弹窗中打开表格以清晰地查看所有数据
| 功能 | next-intlayer (Intlayer) | next-intl |
|---|---|---|
| 组件附近的翻译 | ✅ 是的,.content.ts 与每个组件并置 | ❌ 否,集中的 messages/{locale}.json |
| TypeScript 集成 | ✅ 从内容自动生成的严格类型 | ✅ 良好,通过 global.d.ts 增强类型化的键 |
| 缺失翻译检测 | ✅ TypeScript 错误 + 构建时错误/警告 | ⚠️ 运行时回退 + 控制台警告 |
| 富内容(JSX / Markdown / 组件) | ✅ 直接支持 | ⚠️ t.rich() / t.markup() 配合标签占位符 |
| ICU 支持 | ⚠️ 开发中 | ✅ 是 |
| 格式化(日期、数字、货币) | ✅ useNumber、useDate 等(底层使用 Intl) | ✅ useFormatter()(底层使用 Intl) |
| 本地化路由与中间件 | ✅ 内置代理/中间件,getMultilingualUrls | ✅ 内置中间件,Link,redirect,usePathname |
| SEO 助手(hreflang、sitemap、robots) | ✅ 内置助手 | ⚠️ 手动配置,基于路由配置 |
| 同步服务器组件 | ✅ useIntlayer 来自 next-intlayer/server,适用于任何子服务器组件 | ⚠️ getTranslations 是异步的;同步子组件需要通过 props 传递 t |
| 静态渲染 | ✅ 不阻止静态渲染 | ⚠️ 需要 setRequestLocale();命名空间 catalogs 在我们的测试中仍会使页面退出静态渲染 |
| Tree-shaking(仅发送使用过的内容) | ✅ 按组件、按语言,由编译器自动化 | ⚠️ 手动:命名空间 + 每个页面的 pick(messages, [...]) |
| 懒加载 | ✅ importMode: 'dynamic'(一行配置) | ⚠️ 手动在 getRequestConfig 中进行动态导入 |
| 清除未使用的内容 | ✅ 已过时的字典在构建时被移除 | ❌ 未内置 |
| 测试缺失的翻译 (CLI / CI) | ✅ npx intlayer content test | ⚠️ 未内置;文档建议使用 npx @lingual/i18n-check |
| AI 驱动的翻译 | ✅ 内置,使用您自己的提供者密钥 | ❌ 否 |
| Visual Editor / CMS | ✅ 免费可视化编辑器 + 可选 CMS | ❌ 否(外部本地化平台) |
| MCP server & Agent Skills | ✅ 是 | ❌ 否 |
| Ecosystem / community | ⚠️ 较小但增长迅速 | ✅ 大型,Next.js 的参考标准 |
基准测试
测量的内容
Benchmark Bloom 套件使用每个库构建相同的应用程序:10 个页面(首页、关于、博客、职业、联系、常见问题、定价、产品、设置、团队),10 个语言环境(en、fr、es、de、it、pt、zh、ja、ko、ru),相同的组件和相同的内容。页面在 en 和 fr 中进行测量。每个库实现了多达四种加载策略,从最初的设置到最优的设置:
在弹窗中打开表格以清晰地查看所有数据
| 策略 | 描述 | 谁在这样做 |
|---|---|---|
| static | 每个 locale 和每个页面打包在一起 | 快速原型、AI 生成代码 |
| dynamic | 仅加载活跃的 locale,但同时加载所有页面 | 大多数项目 |
| scoped-static | 按路由命名空间,无懒加载 | 罕见 |
| scoped-dynamic | 按路由命名空间 + 懒加载。仅发送当前 locale 中的当前页面 | 性能预算严格的应用 |
Intlayer 没有"scoped"变体:编译器会自动按组件对内容进行作用域划分,因此其 static 和 dynamic 行已经是作用域的。
对于每次构建,该套件记录:
- Lib size: 仅导入 i18n 库的空组件的 gzip 大小。运行时的固定成本。
- Page JS: 每个页面下载的 gzip JavaScript,在所有页面和语言环境中平均计算。
- Locale leak %: 下载的 JS 中找到的已翻译字符串中属于用户未查看的语言环境的份额(在
en和fr上进行指纹识别,因此 50% 表示"另一个测量的语言环境完全存在";使用 10 个捆绑的语言环境时,实际浪费更高)。 - Page leak %: 下载的 JS 中找到的已翻译字符串中属于用户未浏览的页面的份额。
- Component avg: 各个编译的每个组件的平均 gzip 大小。显示单个组件拖入多少 i18n 运行时。
- E2E reactivity: 选择新locale和
html[lang]在DOM中更新之间的实时时间(Playwright,5次迭代)。 - Hydration: React hydration阶段持续时间。
下面的数字来自2026-09-12运行,使用next-intl4.14.2、use-intl4.14.2和intlayer9.5.1。测试应用程序故意很小(每个locale只有几十个字符串),所以泄漏百分比描述的是一个模式:随着您的内容增长而增长,但运行时成本保持固定。
Next.js (App Router)上的结果
选择您关注的指标和库:
动态 JSON 加载
在运行时懒加载翻译
有作用域的 JSON (命名空间)
每页翻译命名空间
I18n 性能基准测试
这个指标是什么?
国际化库包的总 gzip 压缩大小。它仅包含 tree-shaking 和压缩(minification)后的提供者(provider)和内容检索逻辑。
为什么这很重要?
较小的库大小可减少初始 JavaScript 负载,从而缩短客户端的下载和执行时间。
视图形式
在弹窗中打开表格以清晰地查看所有数据
| Library | Strategy | Lib size (gz) | Page JS avg (gz) | Locale leak | Page leak | Component avg (gz) | E2E reactivity | Hydration |
|---|---|---|---|---|---|---|---|---|
| base (no i18n) | - | 0.0 KB | 141.0 KB | 0.0% | 0.0% | 0.9 KB | 13.4 ms | 11.8 ms |
next-intl | static | 14.7 KB | 153.6 KB | 4.2% | 89.8% | 21.8 KB | 16.0 ms | 14.7 ms |
next-intl | dynamic | 14.7 KB | 153.6 KB | 9.7% | 89.9% | 21.8 KB | 15.6 ms | 14.8 ms |
next-intl | scoped-static | 14.7 KB | 153.6 KB | 0.0% | 0.0% | 80.1 KB | 17.9 ms | 17.4 ms |
next-intl | scoped-dynamic | 14.7 KB | 153.6 KB | 0.0% | 0.0% | 22.9 KB | 17.8 ms | 16.8 ms |
next-intlayer | static | 5.5 KB | 141.3 KB | 0.0% | 0.0% | 8.5 KB | 15.5 ms | 16.9 ms |
next-intlayer | dynamic | 5.5 KB | 141.3 KB | 0.0% | 0.0% | 6.9 KB | 15.3 ms | 15.9 ms |
@intlayer/next-intl (compat) | static | 8.0 KB | 147.5 KB | 0.0% | 0.0% | 8.1 KB | 14.5 ms | 12.8 ms |
@intlayer/next-intl (compat) | dynamic | 8.0 KB | 148.7 KB | 0.0% | 0.0% | 8.1 KB | 11.7 ms | 12.8 ms |
如何阅读
- 运行时成本。 基础应用程序每页重 141.0 KB。
next-intl将其增加到 153.6 KB(每页额外 +12.6 KB gzip),Intlayer 增加到 141.3 KB(额外 +0.3 KB)。这个差异不取决于你有多少个字符串:它是库运行时的成本。 - Leakage. 在两个最多团队实际使用的设置中(
static和dynamic),next-intl会在每个页面上传送 ~90% 的外语页面字符串:整个en.json都会进入客户端 provider。要达到 0% 需要使用scoped-*设置:将目录分割成命名空间,然后在每个页面中pick()正确的。Intlayer 在两行中都是 0%,无需任何额外配置。 - 每页面 JS 大小在不同策略间对
next-intl没有变化。 测试内容很小,所以 ~90% 的泄漏这里只有几 KB。在一个有数百个字符串每页面的真实应用中,这个比例会成为主导成本。同时 +12.6 KB 的 runtime 在每个配置中都会被支付。 - 组件大小。 调用
useTranslations()的组件编译到平均 21.8 KB;使用useIntlayer()的相同组件编译到 6.9 KB。在scoped-static设置中,next-intl组件跳升到 80.1 KB,因为每个组件都内联其命名空间 catalog。 - Reactivity 和 hydration 在 Next.js 上对两个库都处于相同的范围(15-18 ms)。这里都不是瓶颈。
完整表格、每个库和每种策略,请参阅 Next.js 基准测试报告。
TanStack Start 上的结果(use-intl)
use-intl 是 next-intl 的框架无关核心。相同的 API,相同的消息格式。在 TanStack Start 上将其与 intlayer 进行比较,移除了方程的 Next.js 特定部分。
在弹窗中打开表格以清晰地查看所有数据
| Library | Strategy | Lib size (gz) | Page JS avg (gz) | Locale leak | Page leak | Component avg (gz) | E2E reactivity |
|---|---|---|---|---|---|---|---|
| base (no i18n) | - | 0.0 KB | 111.0 KB | 0.0% | 0.0% | 0.7 KB | 8.1 ms |
use-intl | static | 14.1 KB | 179.8 KB | 50.0% | 89.8% | 76.0 KB | 6.7 ms |
use-intl | dynamic | 14.1 KB | 119.4 KB | 0.0% | 89.8% | 75.9 KB | 7.0 ms |
use-intl | scoped-static | 14.1 KB | 128.7 KB | 0.0% | 0.0% | 87.1 KB | 20.9 ms |
use-intl | scoped-dynamic | 14.1 KB | 128.7 KB | 0.0% | 0.0% | 87.1 KB | 13.3 ms |
intlayer | static | 5.0 KB | 125.8 KB | 50.0% | 0.0% | 8.1 KB | 3.2 ms |
intlayer | dynamic | 5.0 KB | 118.6 KB | 0.0% | 0.0% | 6.3 KB | 3.6 ms |
@intlayer/use-intl (compat) | dynamic | 7.3 KB | 129.7 KB | 0.0% | 0.0% | 9.3 KB | 8.7 ms |
如何阅读
- 朴素的
use-intl设置比基础应用多装载了 68.8 KB 的 JS,其中一半的字符串属于错误的语言环境,90% 属于错误的页面。 use-intl在dynamic模式下达到 119.4 KB,接近 Intlayer 的 118.6 KB,但仍然存在 89.8% 页面泄漏:活跃 locale 的所有页面字符串都在每个页面加载。按路由作用域划分(scoped-*)可以消除泄漏,但需要额外支付约 9 KB 的 chunk 开销。- Intlayer 的
static行已经实现 0% 页面泄漏:编译器仅打包页面上的组件使用的字典。启用importMode: 'dynamic'(在intlayer.config.ts中只需一行)也消除了 locale 泄漏。 - 组件大小是架构的亮点:使用
use-intl每个组件 76-87 KB,而使用 Intlayer 仅 6-8 KB。useTranslations()将每个组件绑定到全局消息树;useIntlayer()将其绑定到自己的字典。 - 区域设置切换使用 Intlayer 快 2-4 倍(3 毫秒 vs 7-21 毫秒)。
完整表格请参阅 TanStack Start 基准测试报告。
为什么存在差距?集中式目录 vs. 编译的字典

next-intl 遵循经典模型:每个区域设置一个 JSON,在 getRequestConfig 中加载,推送到 NextIntlClientProvider,通过 t("namespace.key") 读取。
复制代码到剪贴板
运行时无法知道页面将使用哪些 key,因此安全的默认做法是发送整个 catalog。优化意味着你需要将 catalog 拆分成 namespace,你需要决定每个页面需要哪些 namespace,并且你需要在组件移动时保持该映射同步。基准测试中的 scoped-dynamic 行是该工作的回报,但大多数团队从未实现过。
未能实现这一目标的成本在两个维度上同时增长:页面和语言环境:

Intlayer 翻转了这个责任。内容在组件旁边声明:
复制代码到剪贴板
在构建时,编译器(@intlayer/swc / @intlayer/babel)会检查哪个组件导入了哪个字典。它只为活跃的语言环境捆绑那些字典,并删除未被导入的字典。"scoped-dynamic"模式成为构建的输出,而不是团队必须维护的纪律。
要获得dynamic行的数字,请在intlayer.config.ts中设置dictionary.importMode: 'dynamic'。请参阅 bundle 优化文档。
开发者体验
Client 组件
复制代码到剪贴板
复制代码到剪贴板
记住在渲染此组件的每个页面上,将counter命名空间包含在传递给NextIntlClientProvider的消息中。
复制代码到剪贴板
复制代码到剪贴板
无需在页面上注册任何内容:该组件包含其自己的内容。
同步服务器组件
设计系统组件(导航栏、页脚、卡片)通常是作为客户端组件的子组件渲染的服务器组件,因此它们不能是 async。
复制代码到剪贴板
页面必须 await getTranslations("counter") 和 await getFormatter(),然后将结果作为 props 向下传递。该组件不再是自包含的。
复制代码到剪贴板
元数据
复制代码到剪贴板
复制代码到剪贴板
保持 next-intl API,获得 Intlayer 的输出
您不必重写组件来获得上述基准数字。@intlayer/next-intl 是一个开箱即用的适配器:它保持 useTranslations、getTranslations、useFormatter、t.rich()、ICU 复数形式和 next-intl/navigation 助手,并从 Intlayer 编译器编译的 Intlayer 字典中提供它们。
复制代码到剪贴板
在基准测试中,同一应用的兼容性构建从每页 153.6 KB 降至 147.5 KB,从每个组件 21.8 KB 降至 8.1 KB,以及从约 90% 的页面泄漏降至 0%,应用代码保持不变。你现有的 messages/{locale}.json 文件可以通过 JSON 同步插件保持作为真实来源。
查看 next-intl 迁移指南获取分步说明。
何时选择哪个?
您需要 Next.js 的生态标准,依赖 ICU MessageFormat,您的应用规模较小或中等,或者您与需要集中式 JSON 的翻译平台(Crowdin、Phrase、Lokalise...)集成。如果注重性能,请预留时间按命名空间划分目录并在每个页面使用 pick() 选择消息。
您已经在项目中使用了 next-intl,希望在无需重写的情况下获得包体积缩减优势。兼容适配器保留了您的导入和 messages/{locale}.json 文件作为单一真实来源。在 next-intl 对比 @intlayer/next-intl 中进行了同台实测。
常见问题
在渲染阶段不是。两者的差异在于传输内容:next-intl 在每个页面上增加 +12.6 KB gzip 的运行时开销,并且在常见的配置下,每个页面都会携带 ~90% 的无关页面文本。在 Next.js 上语言切换与注水(hydration)耗时相近(15-18 毫秒);在 TanStack Start 上,use-intl 耗时 7-21 毫秒,而 Intlayer 仅需 3-4 毫秒。
可以,通过 scoped-dynamic 配置:将 messages/{locale}.json 按路由拆分为单个命名空间,然后在每个页面中使用 pick(messages, [...]) 并在组件调整时持续维护该映射关系。基准测试中的 scoped-* 行正代表这项工作。而 Intlayer 天然就能达到 0%,因为编译器会在组件层级对内容进行作用域划分。请参阅包体积优化。
不需要。@intlayer/next-intl 保留了 useTranslations、getTranslations、useFormatter、t.rich()、ICU 复数语法和导航助手,并通过编译后的字典进行提供。只需在 next.config.ts 中添加一行插件配置。详情请参阅 next-intl 迁移指南。
原生 API 正在积极完善 ICU 支持。兼容适配器(@intlayer/next-intl、@intlayer/use-intl)均能完美执行 ICU:复数、select、selectordinal、# 和 {ts, date, long} 都会经过 Intlayer 的 ICU 解析器处理。详情请参阅 ICU 消息格式解析。
可以。JSON 同步插件会读取这些文件,将其顶层键拆分为字典,并在 CLI 或 CMS 更新时将翻译写回同一文件。译者的工作流程完全保持不变。
相关比较
同一基准测试,其他库:
- i18next vs Intlayer
- Lingui vs Intlayer
- vue-i18n vs Intlayer benchmark
- next-i18next vs next-intl vs Intlayer
- react-i18next vs react-intl vs Intlayer
深入了解 next-intl:
- next-intl vs @intlayer/next-intl, 在同一应用上实测的适配器
- Is next-intl outdated?
- Using Intlayer with next-intl
- How to internationalize a Next.js app with next-intl
参考文档:
- Next.js 基准测试报告 与 TanStack Start 基准测试报告
- 兼容适配器:next-intl 与 迁移指南
- 包体积优化 与 Intlayer 编译器
- 组件级 vs 集中式 i18n
- 编译器驱动 vs 声明式 i18n
GitHub Stars
GitHub Stars 是项目受欢迎程度、社区信任度和长期相关性的强有力指标。虽然不是技术质量的直接衡量标准,但它们反映了有多少开发者认为该项目有用、关注其进展,以及可能采用它的开发者数量。
结论
next-intl 是一个可靠的、维护良好的库,基准测试证实它远不是 Next.js 上最差的选择。但是它的集中式目录模型将每项优化都交给了开发者:天真的设置会泄漏约 90% 的外语页面内容,而 runtime 本身在每个页面上就花费 +12.6 KB gzip。
Intlayer 将这些工作转移到编译器中。按组件字典、按语言环境的懒加载和无用内容清理是构建输出,而不是约定。在同一个应用上的结果:每页 +0.3 KB,0% 泄漏,组件小 3 倍,以及在 TanStack Start 上语言切换快 2-4 倍。
所有原始数据、测试应用和脚本都在 Benchmark Bloom 仓库 中。自己运行它。
有关更多详细信息,请参阅 '为什么选择 Intlayer?' 文档。
评论
暂无评论。成为第一个分享您想法的人吧。
