このページとあなたの好きなAIアシスタントを使ってドキュメントを要約します
このページのコンテンツはAIを使用して翻訳されました。
英語の元のコンテンツの最新バージョンを見るこのドキュメントを改善するアイデアがある場合は、GitHubでプルリクエストを送信することで自由に貢献してください。
ドキュメントへのGitHubリンクドキュメントのMarkdownをクリップボードにコピー
next-intl VS @intlayer/next-intl | 同じAPI、異なるBundle
@intlayer/next-intl は互換性アダプタです。next-intl API (useTranslations、getTranslations、useLocale、t.rich()、ICU複数形、NextIntlClientProvider...)を公開し、Intlayerによってコンパイルされたディクショナリから提供します。アプリケーションコードは変わりません。bundleが変わります。
この記事は、同じNext.jsアプリケーションで2つを比較しており、1回はnext-intlで構築し、もう1回はアダプタで構築しています。数値はBenchmark Bloomから得られており、これはブラウザが実際にダウンロードするものを記録するオープンソーススイートです。ライブラリとしてnext-intlとIntlayerの比較が必要な場合は、next-intl vs Intlayerをお読みください。このドキュメントは、コンポーネントをそのままにしておいたときにアダプタが何を変更するかについてです。
tl;dr: 同じNext.jsアプリで、next-intlを@intlayer/next-intlに置き換えることで、ページあたりのJavaScriptが153.6 KBから147.5 KB gzipに、平均コンポーネントが21.8 KBから8.1 KBに、外部ページの文字列漏洩が約90%から0%に、ハイドレーションが14.7 msから12.8 msに短縮され、コンポーネント編集なしで達成されました。TanStack Startでは、use-intlの同等物(@intlayer/use-intl)がコンポーネントを76-87 KBから9-11 KBに削減し、ロケール切り替えを7-21 msから4-9 msに短縮しました。アダプターのランタイムコストは8.0 KB(next-intlは14.7 KB、ネイティブnext-intlayerは5.5 KB)です。ナビゲーションとミドルウェアはIntlayerのルーティング設定で再実装されます。ローカライズされたpathnamesは唯一引き継がれない機能です。
@intlayer/next-intlとは何か
next-intl はランタイムです: getRequestConfig はリクエストごとに messages/{locale}.json を読み込み、NextIntlClientProvider がそれをクライアントに送り、useTranslations("about") がレンダリング時にそのオブジェクトからキーを読み込みます。すべての最適化(名前空間、ページごとの pick(messages, [...])、遅延読み込み)はあなたが書く必要があります。
@intlayer/next-intl はそのチェーンの最初と最後の部分を保つ代わりに、中間部分を置き換えます。あなたのコンポーネントは依然として useTranslations("about") を呼び出しますが、受け取る内容はビルド時にコンパイルされた Intlayer 辞書から来ており、そのコンポーネントにスコープされ、アクティブなロケールのみが含まれます。
3 つのメカニズムでこれを実現します:
- Import aliasing.
createNextIntlPlugin()from@intlayer/next-intl/pluginはwithIntlayerをラップし、Webpack / Turbopack aliases を追加します。これにより、next-intl、next-intl/server、next-intl/navigation、next-intl/middlewareが@intlayer/next-intlに解決されます。codebase 内のいかなる import も変更されません。 - JSON as source of truth.
syncJSONplugin は既存のmessages/{locale}.jsonを読み取り、その top-level keys を namespace ごとに 1 つの dictionary に分割し、CLI または CMS がそれらを更新する際に同じファイルに翻訳を書き込みます。translator のワークフローは変わりません。 - Call-site binding。 Intlayer optimize pass (Babel または SWC) は
useTranslations("about")を、aboutdictionary を直接受け取る呼び出しに書き換えます。コンポーネントはもはやグローバルメッセージツリーにアクセスせず、自身のコンテンツにアクセスします。
コードをクリップボードにコピー
コードをクリップボードにコピー
そのリライトが、以下のコンポーネントサイズとページリーケージのカラムが移動する理由です:ページは、それがレンダリングするコンポーネントの辞書のみを取得し、提供されているロケールでのみ取得します。
アダプターが保持、無視、および置き換えないもの
テーブルをモーダルで開き、すべてのデータを明確に表示
next-intl API | @intlayer/next-intlを使用している場合 |
|---|---|
useTranslations("ns") / getTranslations("ns") | ✅ 保持。ビルド時にns辞書にバインドされます。キーはコンテンツに対して型付けされます。 |
getTranslations({ locale, namespace }) | ✅ 保持されています |
t("key", { name }), t.rich(), t.markup(), t.raw() | ✅ 保持されています。ICU plurals、select、selectordinal、#、{ts, date, long} は Intlayer の ICU resolver を通じて実行されます |
useLocale() / getLocale() / setRequestLocale() / setLocale | ✅ 保持されています |
useFormatter() | ✅ 保持されています。dateTime、number、relativeTime、list、dateTimeRange はネイティブな Intl へ橋渡しされます |
NextIntlClientProvider | ✅ 維持。messages、timeZone、now プロップは受け入れられていますが無視されます(開発者向け警告が表示されます) |
getMessages() | ✅ 互換性のため維持;もう必要ありません |
getRequestConfig() in src/i18n.ts | ⚠️ 不要。辞書はビルド時にコンパイルされます;リクエストごとのメッセージ読み込みはありません |
defineRouting() | ✅ 維持。省略されたフィールド(locales、defaultLocale、localePrefix)は intlayer.config.ts から読み込まれます |
createNavigation(), Link, redirect, usePathname, useRouter | ✅ 保持。Intlayer のルーティング設定で再実装されます。routing 引数は受け入れられていますが無視されます |
pathnames (ローカライズされたルート名) | ❌ 型指定用に受け入れられます。補間されません。プレーンパス名を保つか、そのマッピングを Intlayer の rewrite に移動してください |
createMiddleware() | ✅ 保持。Intlayer のプロキシを返します。NEXT_LOCALE cookie を設定するので、useLocale() とあなたのスイッチャーは機能し続けます |
NEXT_LOCALE cookie | ✅ デフォルトで読み込まれます(routing.storage を自分で設定しない限り) |
Bare useTranslations() with no namespace | ⚠️ 動作しますが、呼び出しサイトがバインドされていません: ランタイムレジストリを通じて解決されます。バンドルの利得を得るには namespace を渡してください |
ベンチマーク
測定内容
Benchmark Bloom スイートは、各セットアップで同じアプリケーションをビルドします: 10 ページ (home、about、blog、careers、contact、FAQ、pricing、products、settings、team)、10 locales (en、fr、es、de、it、pt、zh、ja、ko、ru)、同一のコンポーネントと同一のコンテンツ。ページは en と fr で測定されます。
next-intlは4つのローディング戦略で構築されました。単純なセットアップ(messages/{locale}.json全体を読み込む)から最適なもの(ルートごとに1つのnamespace + ページごとのpick())まで。アダプターは単純なセットアップと同じコンポーネントで構築され、next.config.tsとintlayer.config.tsのみが変更されました。「scoped」バリアントはありません。コンパイラがコンポーネントごとにコンテンツをスコープするため、staticとdynamicの行はすでにスコープされています。
各ビルドについて、スイートは以下を記録します:
- Lib size: i18nライブラリのみをインポートする空のコンポーネントのgzipサイズ。ランタイムの固定コスト。
- Page JS: ページごとにダウンロードされるgzip JavaScript。すべてのページとロケールで平均化されます。
- ロケールリーク %: ダウンロードされた JS に含まれる翻訳済み文字列のうち、ユーザーが表示していないロケールに属する文字列の割合。
- ページリーク %: ダウンロードされた JS に含まれる翻訳済み文字列のうち、ユーザーが閲覧していないページに属する文字列の割合。
- Component avg: 個別にコンパイルされた各コンポーネントの平均 gzip サイズ。単一のコンポーネントが i18n ランタイムとカタログにどの程度のオーバーヘッドをもたらすかを示します。
- E2E reactivity: 新しいロケールを選択してから DOM の
html[lang]が更新されるまでの実際の時間 (Playwright、5 回のイテレーション)。 - Hydration: React ハイドレーション フェーズの継続時間。
以下の数値は 2026-09-12 に実施した実行結果です。next-intl/use-intl4.14.2 および@intlayer/*9.5.1 を使用しています。テストアプリケーションは意図的に小規模(ロケールあたり数十の文字列)であるため、リーケージのパーセンテージはパターンを説明しています。コンテンツが増えるにつれてリーケージは増加しますが、runtime のコストは固定のままです。
Next.js での結果
テーブルをモーダルで開き、すべてのデータを明確に表示
| Setup | 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 |
@intlayer/next-intl | static | 8.0 KB | 147.5 KB | 0.0% | 0.0% | 8.1 KB | 14.5 ms | 12.8 ms |
@intlayer/next-intl | dynamic | 8.0 KB | 148.7 KB | 0.0% | 0.0% | 8.1 KB | 11.7 ms | 12.8 ms |
next-intlayer (native) | static | 5.5 KB | 141.3 KB | 0.0% | 0.0% | 8.5 KB | 15.5 ms | 16.9 ms |
next-intlayer (native) | dynamic | 5.5 KB | 141.3 KB | 0.0% | 0.0% | 6.9 KB | 15.3 ms | 15.9 ms |
読み方
- 同じコンポーネント、ページあたり 6 KB 削減。 アダプター ビルドのナイーブなアプリは 147.5 KB に到達し、完全に最適化されたもの (153.6 KB) を含む すべての
next-intl構成の下にあります。ランタイム自体が違い:8.0 KB 対 14.7 KB で、すべてのページで支払われます。 - リークは何も変更することなく 0% に到達します。 ナイーブな
next-intlセットアップは、すべてのページで外国ページ文字列の約 90% を配信しています。next-intlで 0% に到達するには、scoped-*セットアップが必要です:ルートごとに 1 つの namespace、および各ページでpick(messages, [...])を使用します。アダプターは、optimize パスが各useTranslations("ns")をそれ独自の辞書にバインドするため、ナイーブなコードから 0% に到達します。 - コンポーネントは 2.7 倍縮小されます。 分離されたコンパイルされたコンポーネントは、
next-intlの場合は平均 21.8 KB(プロバイダーとメッセージツリーに達します)で、アダプターの場合は 8.1 KB です。next-intlのscoped-staticセットアップでは、その数は 80 KB に上がります。なぜなら、すべてのルートの namespace ファイルがそれを選択するページから到達可能になるからです。 - Hydrationが2ms高速 (12.8 vs 14.7 ms): RSCペイロードから逆シリアル化するメッセージオブジェクトがないため、Reactが水和できます。
- アダプターはネイティブランタイムではありません。
next-intlayerは141.3 KBで、ベースアプリの上に+0.3 KBで、5.5 KBのランタイムを備えています。アダプターはIntlayerのコア上にnext-intlAPIサーフェス(useFormatter、t.rich、ICUリゾルバ)を搭載しており、したがって8.0 KBと1ページあたり+6 KBです。これはブリッジであり、目的地ではありません。
TanStack Start上の結果 (use-intl)
use-intlはnext-intlのフレームワークに依存しないコアです。そのアダプター@intlayer/use-intlは、Viteプラグイン(@intlayer/use-intl/plugin)を使用して同じ設計に従います。
テーブルをモーダルで開き、すべてのデータを明確に表示
| セットアップ | ストラテジー | Lib サイズ (gz) | ページ JS 平均 (gz) | ロケール漏洩 | ページ漏洩 | コンポーネント平均 (gz) | E2E レスポンシビティ | ハイドレーション |
|---|---|---|---|---|---|---|---|---|
| base (i18n なし) | - | 0.0 KB | 111.0 KB | 0.0% | 0.0% | 0.7 KB | 8.1 ms | 21.6 ms |
use-intl | static | 14.1 KB | 179.8 KB | 50.0% | 89.8% | 76.0 KB | 6.7 ms | 15.3 ms |
use-intl | dynamic | 14.1 KB | 119.4 KB | 0.0% | 89.8% | 75.9 KB | 7.0 ms | 15.4 ms |
use-intl | scoped-static | 14.1 KB | 128.7 KB | 0.0% | 0.0% | 87.1 KB | 20.9 ms | 24.8 ms |
use-intl | scoped-dynamic | 14.1 KB | 128.7 KB | 0.0% | 0.0% | 87.1 KB | 13.3 ms | 25.9 ms |
@intlayer/use-intl | static | 7.3 KB | 135.8 KB | 49.7% | 0.0% | 10.9 KB | 4.2 ms | 10.5 ms |
@intlayer/use-intl | dynamic | 7.3 KB | 129.7 KB | 0.0% | 0.0% | 9.3 KB | 8.7 ms | 16.1 ms |
intlayer (native) | static | 5.0 KB | 125.8 KB | 50.0% | 0.0% | 8.1 KB | 3.2 ms | 11.5 ms |
intlayer (native) | dynamic | 5.0 KB | 118.6 KB | 0.0% | 0.0% | 6.3 KB | 3.6 ms | 14.1 ms |
読み方
- ページあたりのバイト数は最適化された
use-intlと同等です。@intlayer/use-intlのdynamicモード (129.7 KB) はuse-intlのscoped-dynamic(128.7 KB) と 1 KB の差以内であり、use-intlの通常のdynamic(119.4 KB) より 10 KB 上回っています。その通常のdynamic行は外部ページの文字列の 90% を依然リークしています。バイト数が低いのはテストアプリのコンテンツが小さいためです。アダプターの 0% はコンテンツが増加しても一定に保たれるものです。 - コンポーネントは7~9倍小さい。
use-intlコンポーネントは平均76~87 KBでこれはすべての戦略で同じです。これはuseTranslationsがプロバイダーの全メッセージオブジェクトにバインドされているためです。アダプターは平均9~11 KBです。 - ロケール切り替えが高速。 最適化された
use-intlセットアップはhtml[lang]を更新するのに13~21 msかかります。アダプターは4~9 msかかります。再レンダリングされるコンポーネントが少なく、メッセージツリーから再取得されるものがありません。 staticはすべてのロケールを保持する。 アダプターのstatic行は49.7%のロケールリークを示しており、これはネイティブIntlayerのstaticモードと同じです。すべてのロケールがバンドルされ、ページの辞書のみが対象です。1行の設定(importMode: 'dynamic')でこれを削除できます。
数字が変わる理由
コンポーネント内では何も変更されていないため、利益はすべてuseTranslationsがバインドされているもの由来です。
next-intlの場合、バインディングはプロバイダーです。NextIntlClientProviderはロケールのmessagesオブジェクト全体を受け取り、すべてのuseTranslations("about")がそこから読み込まれます。バンドラーは1つのコンポーネントが1つのフックをインポートしており、そのフックが1つのコンテキストを読み込んでいることを認識しますが、aboutブランチのみが使用されていることを知ることはできません。以下のルートはすべて同じメッセージオブジェクトを共有しているため、page-leakカラムは自分でファイルを分割するまで〜90%を読み込みます。
コードをクリップボードにコピー
@intlayer/next-intlを使用する場合、バインディングは辞書です。syncJSONはmessages/en.jsonを1つのトップレベルキーごとに1つの辞書に変換します。コンパイラはuseTranslations("about")を呼び出すコンポーネントを解決し、アクティブなロケールでaboutを直接渡します。これはbundlerが追跡して分割できるimportです。
コードをクリップボードにコピー
src/i18n.ts と messages prop は廃止されます。その他はすべて同じです。
3 つのステップでの移行
インストール
bashコードをコピーコードをクリップボードにコピー
このコマンドは
next-intlを検出し、intlayer、next-intlayer、@intlayer/next-intl、@intlayer/sync-json-pluginをインストールします。next-intlをインストール状態のままにしてください。これはアダプターのピア依存関係であり、型を提供しています。Intlayerをメッセージに指す
intlayer.config.tsコードをコピーコードをクリップボードにコピー
messages/{locale}.jsonはそのままの場所に留まります。各トップレベルのキーは辞書になります。useTranslations("about")はabout辞書にマッピングされます。next.config.ts をラップする
next.config.tsコードをコピーコードをクリップボードにコピー
createNextIntlPlugin()はwithIntlayer(コンテンツ監視、辞書コンパイル、最適化パス) と Webpack および Turbopack のnext-intl→@intlayer/next-intlエイリアスを構成します。ビルドすれば、上記の表の数字があなたのものになります。
その後削除できるもの
テーブルをモーダルで開き、すべてのデータを明確に表示
| ファイル / パターン | 理由 |
|---|---|
src/i18n.ts の getRequestConfig | リクエストごとのメッセージ読み込みがありません。ファイルは createNavigation ヘルパーもエクスポートする場合のみ保持してください |
messages={...} on NextIntlClientProvider | アダプターはコンパイル済み出力を読み込みます。このプロップは無視され、開発時に警告がログに記録されます |
await getMessages() in layouts | 同じ理由 |
Per-page pick(messages, [...]) | コンパイラーがコンポーネントごとにピッキングを実行します |
バイト数以上に得られるもの
- 型付きキー。
useTranslations("about")はコンパイル済みabout辞書に対して型チェックされます。t("does.not.exist")はランタイムのフォールバックではなく TypeScript エラーになります。 npx intlayer testはロケールにキーが不足している場合にCIを失敗させます。npx intlayer fillは、選択したプロバイダー(OpenAI、Anthropic、Mistral、Gemini など)を使用して不足しているキーを翻訳し、独自のキーを使用して結果をmessages/{locale}.jsonに書き込みます。- Visual Editor と CMS は同じ辞書を操作するため、開発者以外のユーザーは UI を通じて
messages/fr.jsonを編集でき、ファイルが更新されます。 .content.tsへの段階的な移行。 どのコンポーネントでも、useTranslations("about")からuseIntlayer("about")へ、コンポーネントの近くにあるコンテンツファイルとともに、一度に 1 つずつ切り替えることができます。JSON と.content.tsの辞書は共存してマージされます。
開始する前に知っておくべき制限事項
- ルーティング設定が
intlayer.config.tsに移動します。createNavigation(routing)とcreateMiddleware(routing)はシグネチャを保持しますが、引数を無視します。ロケール、デフォルトロケール、プレフィックス戦略は Intlayer のrouting設定から取得されます。next-intlのローカライズされたpathnames(/about→/a-propos)を使用している場合、アダプタはそれらをインターポレートしません。Intlayer のrouting.rewriteがそのケースをカバーしますが、これは別の変更です。 - 名前空間なしの
useTranslations()はバインドされません。 最適化パスは、どの辞書をインポートするかを知るための静的な名前空間が必要です。ベアコールは依然として機能しますが、すべての辞書を参照するランタイムレジストリを通じて機能します。これは正確に削除しようとしていたリークです。名前空間を渡してください。 - アダプターは無料ではありません。
next-intlayerの 5.5 KB に対して 8.0 KB のランタイム、ネイティブビルドよりページあたり +6-7 KB です。これはnext-intlの API サーフェスの代償です。すべてのコンポーネントがuseIntlayerに移行した時点で、アダプターを削除してください。 - プロバイダーの
messages、timeZone、nowは無視されます。 フォーマッターはネイティブのIntlによってバックアップされており、ロケールのみがその出力に影響します。ハイドレーション安定日時のために強制タイムゾーンや固定のnowに依存している場合は、呼び出しサイトで処理してください。
どれを使うべきか?
next-intlに留まってください アプリが小規模で、バンドルサイズが問題でなく、チームがネームスペースとページごとのpick()の管理に問題がない場合。@intlayer/next-intlを使用する 現在next-intlを使用していて、バンドルサイズの削減、リークの防止、ハイドレーションの改善、型安全なキー、CLIおよびCMSツールが必要で、書き直したくない場合に推奨されます。これは既存のnext-intlcodebaseへの推奨エントリーポイントです。- ネイティブに移行する(
next-intlayer) 新しいプロジェクト、またはアダプターがその役割を終えた後に推奨されます。3つの中で最も軽量(5.5 KB、ページあたり+0.3 KB)で、同期型サーバーコンポーネント、コンポーネント単位の.content.tsファイル、および全機能セットをアンロックします。
関連する比較
- next-intl vs Intlayer (ライブラリ、同じベンチマーク)
- i18next vs @intlayer/i18next (同じアダプターシリーズ)
- Lingui vs @intlayer/lingui (同じアダプターシリーズ)
- vue-i18n vs @intlayer/vue-i18n (同じアダプターシリーズ)
- 移行ガイド: next-intl to Intlayer
- 互換性アダプターリファレンス: next-intl
結論
@intlayer/next-intl は 1 つのことを行います。useTranslations をバインドする対象を、すべてのメッセージを保持するプロバイダーから、そのコンポーネント用にコンパイルされた dictionary に変更します。1 ページあたり 6 KB の価値がある同じ Next.js アプリで、2.7 倍小さいコンポーネント、0% のリーケージ と 2 ms のハイドレーション が実現でき、誰もコンポーネント ファイルを開く前に達成されます。Navigation と middleware は Intlayer のルーティング設定の上で API を維持し、ネイティブ next-intlayer ランタイムはさらに軽いままです。
すべてのraw data、テスト アプリ、およびスクリプトは Benchmark Bloom リポジトリ にあります。自分で実行してください。
詳細については、'Why Intlayer?' ドキュメント を参照してください。
コメント
まだコメントはありません。最初のコメントを共有しましょう。
