著者:
    作成:2026-09-13最終更新:2026-09-13

    i18next VS Intlayer | React & Next.js 国際化 (i18n) ベンチマーク比較

    i18nextは、JavaScriptエコシステムで最も広く利用されているi18nフレームワークです。react-i18nextnext-i18nextを通じて、数多くのReactおよびNext.jsアプリケーションで採用されています。Intlayerは、コンパイラ駆動型でコンポーネントスコープ設計を採用した新しい選択肢です。

    本記事では、機能リストの単なる比較ではなく、客観的な実測値に基づいて両者を比較します。掲載されている数値は、同一アプリケーションを各ライブラリでビルドし、ブラウザが実際にダウンロードするリソースを記録するオープンソーススイートBenchmark Bloomの測定結果に基づいています。

    tl;dr: i18nextはベンチマーク内で最も重量級のランタイムであり、標準構成のNext.jsで1ページあたり+77 KB gzip、名前空間と遅延ロードを完全に最適化した場合でも+22 KBの増加をもたらします。対してIntlayerの増加はわずか+0.3 KBです。完全スコープ化された構成を除くすべてのi18next構成では、別ページの翻訳文字列が約90%リークして送信されますが、Intlayerはデフォルトで0%です。遅延読み込みバックエンドを使用したロケール切り替えは、react-i18next123〜185 msかかったのに対し、Intlayerは3〜4 msで完了しました。i18nextのAPIを維持できる互換アダプター@intlayer/next-i18nextを使用した場合でも、1ページあたり150.7 KBとなり、本家の218.5 KBから大幅に削減されます。

    要約

    • i18next / react-i18next / next-i18next - 実績豊富でプラグインが充実し、フレームワークに依存しません。名前空間、言語検出、各種バックエンド、プラグイン経由のICU、リッチコンテンツ用の<Trans>をサポート。翻訳はlocales/{lng}/{ns}.jsonに一括管理されます。強力ですが、最適化(名前空間の分割、ページごとの読み込み制御、型安全性)のすべてを開発者自身で設計・保守する必要があります。
    • Intlayer - コンポーネント中心のコンテンツモデル。.content.ts辞書ファイルを対応するコンポーネントと同階層に配置。ビルド時コンパイラがコンポーネントおよびロケール単位でツリーシェイキングと遅延読み込みを実施し、コンテンツから厳格なTypeScript型を自動生成。翻訳漏れはビルド時に検出・遮断されます。ミドルウェア、SEOヘルパー、ビジュアルエディタ / CMS、AI翻訳支援も標準提供。
    ライブラリGitHubスター総コミット数最終コミット初回リリースNPMバージョンNPM月間ダウンロード数
    aymericzip/intlayerGitHub Repo starsGitHub commit activityLast Commit2024年4月npmnpm downloads
    i18next/i18nextGitHub Repo starsGitHub commit activityLast Commit2012年1月npmnpm downloads
    i18next/react-i18nextGitHub Repo starsGitHub commit activityLast Commit2015年12月npmnpm downloads
    i18next/next-i18nextGitHub Repo starsGitHub commit activityLast Commit2018年11月npmnpm downloads
    バッジは自動更新されます。測定時期により変動します。

    機能比較一覧

    機能Intlayer (react-intlayer / next-intlayer)i18next (react-i18next / next-i18next)
    コンポーネント同階層での翻訳定義✅ 可能(各コンポーネントと同階層に.content.ts❌ 不可(locales/{lng}/{ns}.jsonに集中管理)
    TypeScript統合✅ コンテンツから厳密な型を自動生成⚠️ 基礎的(CustomTypeOptions拡張とリソース型定義が必要)
    翻訳漏れの検知✅ TypeScriptエラー + ビルド時の警告・エラー⚠️ ランタイム時のフォールバック(saveMissing、キー表示)
    リッチコンテンツ(JSX / Markdown)✅ ネイティブ対応⚠️ インデックス付きプレースホルダーによる<Trans>
    ICUメッセージ形式⚠️ 開発中⚠️ プラグイン経由(i18next-icu
    複数形処理✅ 列挙ベースのパターン_one / _other 接尾辞(Intl.PluralRules)
    フォーマット(日付、数値、通貨)useNumberuseDate 等(内部でIntl利用)⚠️ 補間フォーマッターまたは手動のIntl.*呼び出し
    ローカライズされたルーティングとミドルウェア✅ 組み込みプロキシ / ミドルウェア、getMultilingualUrls⚠️ 標準外(独自実装またはサードパーティ製ミドルウェアが必要)
    SEOヘルパー(hreflang、サイトマップ等)✅ 標準提供❌ 手動実装
    同期サーバーコンポーネントnext-intlayer/serveruseIntlayerを任意のServer Componentで直接利用可能⚠️ ページでgetFixedTを呼び出し、Propsでtをバケツリレー
    ツリーシェイキング(使用コンテンツのみ配信)✅ コンポーネントおよびロケール単位でコンパイラが自動処理⚠️ 手動(名前空間分割 + ページごとのnsリスト + バックエンド)
    遅延ロード(Lazy Loading)importMode: 'dynamic'(設定ファイルに1行追記)✅ バックエンドプラグイン経由(i18next-resources-to-backend等)
    未使用コンテンツのパージ✅ 参照されていない辞書はビルド時に破棄❌ 標準機能なし
    翻訳漏れテスト(CLI / CI)npx intlayer content test⚠️ i18next-parserなどのサードパーティツール
    AI翻訳機能✅ 標準搭載(独自のAPIキーを使用)❌ なし(Locize等の外部有償サービスを利用)
    ビジュアルエディタ / CMS✅ 無料のビジュアルエディタ + 任意利用可能なCMS❌ なし(Locize等の外部プラットフォーム)
    MCPサーバー & Agent Skills✅ 対応❌ 非対応
    エコシステム / コミュニティ規模⚠️ 比較的新しいが急速に拡大中✅ 最も大規模で成熟

    ベンチマーク測定

    測定対象と環境

    Benchmark Bloomスイートは、各ライブラリを用いて同一仕様のアプリケーションを構築します。10ページ(home, about, blog, careers, contact, FAQ, pricing, products, settings, team)、10ロケールen, fr, es, de, it, pt, zh, ja, ko, ru)、同一コンポーネント・同一コンテンツで検証し、測定はenおよびfrで行われます。

    各ライブラリは、基本的な構成から最適な構成まで最大4つの読み込み戦略でテストされています:

    戦略概要想定ケース
    static全言語・全ページの翻訳を初期バンドルに内包(init()resourcesを直接指定)プロトタイプ開発、AI生成コード
    dynamicバックエンド経由でアクティブ言語のみを読み込むが、全名前空間を一括ロード一般的な多くのプロジェクト
    scoped-staticルートごとに名前空間を分割するが、すべてビルド時にバンドル稀なケース
    scoped-dynamicルートごとの名前空間分割 + バックエンド遅延ロード。現在のページ・現在の言語のみを読み込む厳格なパフォーマンス予算を持つ大規模アプリ

    Intlayerには「scoped」バリアントが存在しません。コンパイラが自動的にコンポーネント単位でコンテンツをスコープ化するため、staticおよびdynamicの行がすでに最適化された状態となります。

    各ビルドで測定される指標:

    • Lib size: i18nライブラリのみをインポートした空コンポーネントのgzipサイズ。ランタイムの固定コスト。
    • Page JS: 1ページあたりにダウンロードされるJavaScriptのgzipサイズ(全ページ・全言語の平均)。
    • Locale leak %: ダウンロードされたJS内の翻訳文字列のうち、ユーザーが閲覧していない言語の割合。
    • Page leak %: ダウンロードされたJS内の翻訳文字列のうち、ユーザーが開いていないページの割合。
    • Component avg: 独立してコンパイルされた各コンポーネントの平均gzipサイズ。
    • E2E reactivity: 言語切り替え操作からDOMのhtml[lang]が更新されるまでの実測時間(Playwright、5回試行の平均)。
    • Hydration: Reactのハイドレーションフェーズの所要時間。
    以下の測定値は、next-i18next 16.3.0、react-i18next 17.0.13、intlayer 9.5.1を使用した2026-09-12時点のデータです。テストアプリは意図的に軽量(言語あたり数十個の文字列)に作られているため、リーク率は構造的な傾向を表しています。コンテンツ量が増加するにつれてリーク量は拡大しますが、ランタイムコストは固定されます。

    Next.jsでの結果 (next-i18next)

    ライブラリ戦略Lib size (gz)Page JS avg (gz)Locale leakPage leakComponent avg (gz)E2E応答性Hydration
    base (i18nなし)-0.0 KB141.0 KB0.0%0.0%0.9 KB13.4 ms11.8 ms
    next-i18nextstatic19.7 KB218.5 KB0.0%89.8%78.5 KB16.4 ms15.6 ms
    next-i18nextdynamic19.7 KB169.5 KB50.0%89.8%26.1 KB15.4 ms27.7 ms
    next-i18nextscoped-static19.7 KB220.1 KB0.0%89.8%78.9 KB16.4 ms14.7 ms
    next-i18nextscoped-dynamic19.7 KB163.4 KB0.0%0.0%27.1 KB15.9 ms15.1 ms
    next-intlayerstatic5.5 KB141.3 KB0.0%0.0%8.5 KB15.5 ms16.9 ms
    next-intlayerdynamic5.5 KB141.3 KB0.0%0.0%6.9 KB15.3 ms15.9 ms
    @intlayer/next-i18next (互換)static9.4 KB150.7 KB0.0%0.0%9.7 KB10.7 ms11.3 ms
    @intlayer/next-i18next (互換)dynamic9.4 KB150.7 KB0.0%0.0%9.7 KB11.9 ms10.6 ms

    結果の解説

    • ランタイムコスト: i18nextコア + react-i18nextは、空のコンポーネントでも19.7 KB gzipと測定対象中最大で、next-intlayerの5.5 KBを大きく上回ります。
    • 標準構成の肥大化: init()resourcesをインライン化すると、ベースアプリから+77.5 KB増の218.5 KB/ページに達します。全ページが全名前空間を持ち歩くためです。
    • 最適化の難易度: バックエンド(dynamic)に切り替えると49 KB削減できますが、依然として別ページの文字列が90%リークし、測定構成では半分が別言語の文字列となります。ルート単位の名前空間分割(scoped-dynamic)を導入してようやくリーク0%(163.4 KB)に達しますが、設定不要なIntlayerの141.3 KBより22.4 KB重い結果となります。
    • コンポーネントサイズ: useTranslation()を使用するコンポーネントは構成に応じて26〜79 KBに膨らみますが、useIntlayer()を使う同一コンポーネントは6.9 KBで済みます。
    • ハイドレーション: dynamic構成では27.7 msに悪化します。クライアント側でReactがハイドレーションを開始する前に、i18nextインスタンスが初期化されバックエンドを解決する必要があるためです。

    TanStack Startでの結果 (react-i18next)

    Next.js特有のオーバーヘッドを排除するため、TanStack Start上で純粋なreact-i18nextを使用した比較です。

    ライブラリ戦略Lib size (gz)Page JS avg (gz)Locale leakPage leakComponent avg (gz)E2E応答性Hydration
    base (i18nなし)-0.0 KB111.0 KB0.0%0.0%0.7 KB8.1 ms21.6 ms
    react-i18nextstatic18.4 KB180.3 KB50.0%89.8%24.3 KB12.9 ms85.1 ms
    react-i18nextdynamic18.4 KB136.4 KB23.1%89.8%24.8 KB123.1 ms32.9 ms
    react-i18nextscoped-static18.4 KB184.2 KB50.7%89.8%25.3 KB185.1 ms25.2 ms
    react-i18nextscoped-dynamic18.4 KB127.2 KB0.0%0.0%26.7 KB17.6 ms11.3 ms
    intlayerstatic5.0 KB125.8 KB50.0%0.0%8.1 KB3.2 ms11.5 ms
    intlayerdynamic5.0 KB118.6 KB0.0%0.0%6.3 KB3.6 ms14.1 ms

    結果の解説

    • 初期のreact-i18nextアプリはベースアプリより+69 KB/ページ重く、ハイドレーションに85 ms(ベースの4倍)かかります。初回の描画前にすべてのリソースツリーを解析・登録するためです。
    • 言語切り替え時の遅延: バックエンドによる遅延読み込みを行う場合、言語を変更するとネットワーク往復が発生してからhtml[lang]が更新されるため、dynamic123 msscoped-static185 msを要します。対してIntlayerはどちらのモードでも3〜4 msで即座にDOMが反映されます。
    • 最大限に最適化したscoped-dynamicでも127.2 KBと、Intlayerのdynamicより+8.6 KB重く、ルートと名前空間のマッピングやSuspenseの設定などの複雑な作業が必要です。
    • Intlayerのstaticは、該当ページのコンポーネントがインポートした辞書のみをバンドルするため、初期状態でページリーク0%です。importMode: 'dynamic'を設定すれば、言語リークも0%になります。
    • コンポーネントサイズ: react-i18nextの24〜27 KBに対し、Intlayerは6〜8 KBです。

    なぜこれほどの差が出るのか? グローバルインスタンス vs コンパイル済み辞書

    i18nextは2012年にランタイム主導で設計されました。グローバルインスタンスがリソースストアを保持し、プラグインがそれを拡張し、描画時にt()がキーを検索します。この構造が高い汎用性をもたらす一方で、オーバーヘッドの原因となっています:

    bash
    .
    ├── i18n.ts                      # createInstance().use(...).use(...).init({...})
    └── src
        ├── locales
       ├── en
       ├── common.json
       ├── home.json
       └── about.json
       └── fr
           ├── common.json
           ├── home.json
           └── about.json
        ├── components
       └── Counter.tsx          # useTranslation("about") + t("counter.label")
        └── app
            └── [locale]
                └── about
                    └── page.tsx     # ["common", "about"]が必要であることを把握しておく必要がある
    

    インスタンスはコンポーネントがどのキーを必要とするか事前に知ることができないため、指定された名前空間をすべて保持します。最適化のためには、開発者自身がカタログを名前空間に分割し、ページごとの依存関係を定義し、コンポーネントの移動に合わせてメンテナンスし続ける必要があります。ベンチマークノートにある通り、「型安全性を保ちながら各ページに必要な名前空間を正確に把握・維持することは非常に過酷な作業」です。

    Intlayerはグローバルインスタンスを排除します。コンテンツはコンポーネントのすぐ隣で宣言され、コンパイラがビルド時に依存関係グラフを解決します:

    bash
    .
    ├── intlayer.config.ts
    └── src
        ├── components
       └── Counter
           ├── index.tsx        # useIntlayer("counter")
           └── index.content.ts
        └── app
            └── [locale]
                └── about
                    ├── page.tsx
                    └── page.content.ts
    

    @intlayer/swc / @intlayer/babelがコンポーネントと辞書の依存関係を追跡し、アクティブな言語に必要な辞書のみをバンドルし、未使用のコンテンツを切り捨てます。「scoped-dynamic」な最適化は、手作業のルールではなくビルドの成果物として自動的に実現されます。

    dynamic行の数値を再現するには、intlayer.config.tsdictionary.importMode: 'dynamic'を設定します。詳細はバンドル最適化ドキュメントを参照してください。

    開発者体験(DX)

    セットアップの比較

    next-i18next (App Router)

    src/app/i18n/server.ts
    import { createInstance } from "i18next";
    import { initReactI18next } from "react-i18next/initReactI18next";
    import resourcesToBackend from "i18next-resources-to-backend";
    import { defaultLocale } from "@/i18n.config";
    
    const backend = resourcesToBackend(
      (locale: string, namespace: string) =>
        import(`../../locales/${locale}/${namespace}.json`)
    );
    
    export const initI18next = async (
      locale: string,
      namespaces: string[] = ["common"]
    ) => {
      const i18n = createInstance();
      await i18n
        .use(initReactI18next)
        .use(backend)
        .init({
          lng: locale,
          fallbackLng: defaultLocale,
          ns: namespaces,
          defaultNS: "common",
          interpolation: { escapeValue: false },
          react: { useSuspense: false },
        });
      return i18n;
    };
    

    これに加えて、クライアント側のI18nProviderの作成、generateStaticParamsの設定、各ページでの名前空間リストの指定が必要です。

    Intlayer

    intlayer.config.ts
    import { type IntlayerConfig, Locales } from "intlayer";
    
    const config: IntlayerConfig = {
      internationalization: {
        locales: [Locales.ENGLISH, Locales.FRENCH],
        defaultLocale: Locales.ENGLISH,
      },
    };
    
    export default config;
    
    src/app/[locale]/layout.tsx
    import { getHTMLTextDir } from "intlayer";
    import { IntlayerClientProvider, type NextLayoutIntlayer } from "next-intlayer";
    
    const LocaleLayout: NextLayoutIntlayer = async ({ children, params }) => {
      const { locale } = await params;
    
      return (
        <html lang={locale} dir={getHTMLTextDir(locale)}>
          <body>
            <IntlayerClientProvider locale={locale}>
              {children}
            </IntlayerClientProvider>
          </body>
        </html>
      );
    };
    
    export default LocaleLayout;
    

    クライアントコンポーネント

    react-i18next

    src/locales/en/about.json
    {
      "counter": {
        "label": "Counter",
        "increment": "Increment"
      }
    }
    
    src/components/Counter.tsx
    "use client";
    
    import { useState } from "react";
    import { useTranslation } from "react-i18next";
    
    export const Counter = () => {
      const { t, i18n } = useTranslation("about");
      const [count, setCount] = useState(0);
      const numberFormat = new Intl.NumberFormat(i18n.language);
    
      return (
        <div>
          <p>{numberFormat.format(count)}</p>
          <button
            aria-label={t("counter.label")}
            onClick={() => setCount((c) => c + 1)}
          >
            {t("counter.increment")}
          </button>
        </div>
      );
    };
    
    このコンポーネントを描画するページはabout名前空間をロードする必要があり、CustomTypeOptionsを拡張しない限りt("counter.label")の型安全性は保証されません。

    Intlayer

    src/components/Counter/index.content.ts
    import { t, type Dictionary } from "intlayer";
    
    const counterContent = {
      key: "counter",
      content: {
        label: t({ en: "Counter", fr: "Compteur" }),
        increment: t({ en: "Increment", fr: "Incrémenter" }),
      },
    } satisfies Dictionary;
    
    export default counterContent;
    
    src/components/Counter/index.tsx
    "use client";
    
    import { useState } from "react";
    import { useIntlayer } from "next-intlayer";
    import { useNumber } from "next-intlayer/format";
    
    export const Counter = () => {
      const { label, increment } = useIntlayer("counter");
      const number = useNumber();
      const [count, setCount] = useState(0);
    
      return (
        <div>
          <p>{number(count)}</p>
          <button aria-label={label} onClick={() => setCount((c) => c + 1)}>
            {increment}
          </button>
        </div>
      );
    };
    

    labelincrementは厳格に型付けされており、キーのタイポはTypeScriptエラーになり、フランス語の翻訳欠落はビルド時にエラーとして検出されます。

    同期サーバーコンポーネント

    next-i18next

    src/components/ServerCounter.tsx
    type ServerCounterProps = {
      t: (key: string) => string;
      locale: string;
      count: number;
    };
    
    export const ServerCounter = ({ t, locale, count }: ServerCounterProps) => (
      <div>
        <p>{new Intl.NumberFormat(locale).format(count)}</p>
        <button aria-label={t("counter.label")}>{t("counter.increment")}</button>
      </div>
    );
    

    ページ側でi18n.getFixedT(locale, "about")を呼び出し、Propsを通じてtlocaleを渡す必要があります。

    Intlayer

    src/components/ServerCounter.tsx
    import { useIntlayer } from "next-intlayer/server";
    import { useNumber } from "next-intlayer/server/format";
    
    export const ServerCounter = ({ count }: { count: number }) => {
      const { label, increment } = useIntlayer("counter");
      const number = useNumber();
    
      return (
        <div>
          <p>{number(count)}</p>
          <button aria-label={label}>{increment}</button>
        </div>
      );
    };
    

    i18nextのAPIのままIntlayerの最適化を享受する

    既存のコンポーネントを書き換えることなく、上記のベンチマーク性能を得ることも可能です。@intlayer/i18next@intlayer/react-i18next@intlayer/next-i18nextはドロップイン互換アダプターとして機能します。useTranslationt()<Trans>{{interpolation}}、複数形接尾辞、コンテキスト接尾辞、returnObjectsがそのまま動作し、背後ではIntlayerコンパイラが辞書を最適化して供給します。

    next.config.ts
    import type { NextConfig } from "next";
    import { createNextI18nPlugin } from "@intlayer/next-i18next/plugin";
    
    const withIntlayer = createNextI18nPlugin();
    
    const nextConfig: NextConfig = {};
    
    export default withIntlayer(nextConfig);
    
    vite.config.ts
    import { defineConfig } from "vite";
    import { reactI18nextVitePlugin } from "@intlayer/react-i18next/plugin";
    
    export default defineConfig({
      plugins: [reactI18nextVitePlugin()],
    });
    

    ベンチマークでは、アプリコードを変更することなく、Next.jsアプリが1ページあたり218.5 KBから150.7 KBへ、コンポーネントが78.5 KBから9.7 KBへ、ページリークが~90%から0%へと改善し、ハイドレーション時間も15.6 msから11.3 msへ短縮されました。既存のlocales/{lng}/{ns}.jsonは、JSON同期プラグインを介してマスターデータのまま運用できます。

    詳細は移行ガイドをご覧ください: i18next, react-i18next, next-i18next

    どちらを選ぶべきか?

    • i18nextを選ぶべき場合: 豊富なプラグインエコシステム(各種バックエンド、ICU、Locize等)が不可欠な場合、React以外の領域(Node.jsサービス、Vanilla JS、他フレームワーク)でも同一ツールを使いたい場合、チームがすでに習熟している場合、翻訳プラットフォームがlocales/{lng}/{ns}.jsonを前提としている場合。ただし、パフォーマンスを追求する場合は名前空間の設計やページマッピングの維持に十分なリソースを確保してください。
    • Intlayerを選ぶべき場合: コンポーネントスコープのコンテンツ管理厳格なTypeScript型付けビルド時の翻訳漏れ検出自動ツリーシェイキングと遅延ロード、瞬時のロケール切り替え、同期サーバーコンポーネント、統合編集ツール(ビジュアルエディタ、CMS、AI翻訳支援、MCPサーバー)を重視する場合。モジュール化されたコードベースやデザインシステムに最適です。
    • @intlayer/*-i18nextアダプターを選ぶべき場合: すでにi18nextを採用しており、大規模なコード書き換えなしにバンドルサイズと応答性の向上を図りたい場合。

    関連する比較記事

    GitHubスター

    GitHubスターは、プロジェクトの認知度、コミュニティの信頼、長期的な継続性の目安となります。技術的な優劣を直接測るものではありませんが、どれだけの開発者が関心を持ち、実務で採用しているかを反映しています。

    スター履歴グラフ

    結論

    i18nextは10年以上にわたるメンテナンスと抜群の汎用性により、現在の地位を確立しました。しかし、今回のベンチマークはそのランタイム依存型アーキテクチャのコストを浮き彫りにしています。一般的な構成では1ページあたり+70〜77 KB gzipが加算され、別ページの文字列が約90%漏洩し、遅延ロード時の言語切り替えに100 ms以上かかります。リークを0%に抑えることも可能ですが、それには手動での名前空間マッピングが必要であり、それでもなおIntlayerより9〜22 KB重くなります。

    Intlayerはこの負担をコンパイラに移行させました。コンポーネント単位の辞書分割、ロケールごとの遅延ロード、未使用コンテンツのパージは、手作業のルールではなくビルドの成果物として自動化されます。同一アプリでの実測値は、1ページあたり+0.3 KBリーク0%、コンポーネントサイズ3〜10倍削減、ロケール切り替え3〜4 msです。

    テストアプリ、生データ、自動化スクリプトはすべてBenchmark Bloomリポジトリで公開されています。ぜひご自身で追試してみてください。

    詳細は'Why Intlayer?' ドキュメントをご覧ください。

    コメント

    まだコメントはありません。最初のコメントを共有しましょう。

    関連記事

    最新の投稿