著者:
    作成:2025-11-25最終更新:2026-08-09

    i18nバンドルサイズとパフォーマンスの最適化

    JSONファイルに依存する従来のi18nソリューションで最も一般的な課題の1つは、コンテンツサイズの管理です。開発者が手動でコンテンツを名前空間に分割しない場合、ユーザーは1つのページを表示するためだけに、すべてのページ、さらにすべての言語の翻訳をダウンロードすることになることがよくあります。

    たとえば、10言語に翻訳された10ページのアプリケーションの場合、ユーザーは1つ(現在の言語での現在のページ)しか必要としないにもかかわらず、100ページ分のコンテンツをダウンロードすることになります。これは、帯域幅の無駄遣いと読み込み時間の遅延につながります。

    Intlayerは、ビルド時の最適化によってこの問題を解決します。 コードを分析して各コンポーネントで実際に使用されている辞書を検出し、必要なコンテンツのみをバンドルに再注入します。

    目次

    バンドルの分析

    バンドルを分析することは、「重い」JSONファイルやコード分割の機会を特定するための第一歩です。これらのツールは、アプリケーションのコンパイル済みコードの視覚的なツリーマップを生成し、どのライブラリが最もスペースを消費しているかを正確に確認できるようにします。

    Vite / Rollup

    Viteは内部でRollupを使用しています。rollup-plugin-visualizerを使用すると、グラフ内のすべてのモジュールのサイズを示すインタラクティブなHTMLファイルが生成されます。

    bash
    npm install -D rollup-plugin-visualizer
    
    vite.config.ts
    import { defineConfig } from "vite";
    import { visualizer } from "rollup-plugin-visualizer";
    
    export default defineConfig({
      plugins: [
        visualizer({
          open: true, // ブラウザでレポートを自動的に開く
          filename: "stats.html",
          gzipSize: true,
          brotliSize: true,
        }),
      ],
    });
    

    Next.js (Turbopack)

    App RouterとTurbopackを使用しているプロジェクトの場合、Next.jsは追加の依存関係を必要としない組み込みの実験的なアナライザーを提供しています。

    bash
    npx next experimental-analyze
    

    Next.js (Webpack)

    Next.jsでデフォルトのWebpackバンドラーを使用している場合は、公式のバンドルアナライザーを使用してください。ビルド中に環境変数を設定することでトリガーされます。

    bash
    npm install -D @next/bundle-analyzer
    
    next.config.js
    const withBundleAnalyzer = require("@next/bundle-analyzer")({
      enabled: process.env.ANALYZE === "true",
    });
    
    module.exports = withBundleAnalyzer({
      // Next.jsの設定
    });
    

    使用方法:

    bash
    ANALYZE=true npm run build
    

    標準の Webpack

    Create React App (ejected)、Angular、またはカスタムのWebpackセットアップの場合は、業界標準の webpack-bundle-analyzer を使用します。

    bash
    npm install -D webpack-bundle-analyzer
    
    webpack.config.ts
    import { BundleAnalyzerPlugin } from "webpack-bundle-analyzer";
    
    export default {
      plugins: [
        new BundleAnalyzerPlugin({
          analyzerMode: "static",
          reportFilename: "bundle-analyzer.html",
          openAnalyzer: false,
        }),
      ],
    };
    

    仕組み

    Intlayerはコンポーネントごとのアプローチを使用します。グローバルなJSONファイルとは異なり、コンテンツはコンポーネントの横、またはコンポーネント内に定義されます。ビルドプロセス中に、Intlayerは以下を実行します。

    1. 分析: コードを分析して、useIntlayer の呼び出しを見つけます。
    2. 構築: 対応する辞書コンテンツを構築します。
    3. 置換: 設定に基づいて、useIntlayer の呼び出しを最適化されたコードに置き換えます。

    これにより、以下が保証されます。

    • コンポーネントがインポートされていない場合、そのコンテンツはバンドルに含まれません(デッドコードの削除)。
    • コンポーネントが遅延読み込みされる場合、そのコンテンツも遅延読み込みされます。

    プラグインリファレンス

    Intlayerのビルド最適化は、それぞれ単一の責任を持つ複数の個別のプラグインに分割されています。それぞれが何を行うかを理解することで、設定時の混乱を防ぐことができます。

    Babel プラグイン (@intlayer/babel)

    これらは、Webpackベースのセットアップ(Babelを使用したNext.js、CRA、カスタムWebpackなど)の babel.config.js で直接使用されます。

    以下の表は、必要なパイプライン順(babel.config.js に記述しなければならない順序と同じ)で列挙しています:

    プラグイン 役割
    intlayerExtractBabelPlugin .content.ts ファイルをスキャンし、コンパイルされた辞書を .intlayer/ に書き込みます
    intlayerPurgeBabelPlugin すべてのソースファイルをスキャンし、コンパイルされた .intlayer/**/*.json から未使用のフィールドを削除します
    intlayerMinifyBabelPlugin JSONファイルとソースコードの両方で、コンテンツフィールドキーを短いアルファベットのエイリアス(例:titlea)に名前変更します
    intlayerOptimizeBabelPlugin useIntlayer('key')useDictionary(hash) に書き換え、一致する辞書の import を注入します
    プラグインの順序は重要です。 babel.config.js では、purgeとminifyのプラグインは、optimizeのプラグインのに記述する必要があります。最適化パスは useIntlayer('key') を不透明な useDictionary(hash) 呼び出しに置き換えるため、purgeパスとminifyパスが使用されているフィールドを識別するために必要な辞書キー情報が消去されてしまいます。

    各Babelプラグインには、設定読み込み時に intlayer.config.ts を1回読み込み、事前解決された値を返す対応するオプションヘルパーがあります。

    オプションヘルパー 一緒に使用するプラグイン
    getExtractPluginOptions() intlayerExtractBabelPlugin
    getPurgePluginOptions() intlayerPurgeBabelPlugin
    getMinifyPluginOptions() intlayerMinifyBabelPlugin
    getOptimizePluginOptions() intlayerOptimizeBabelPlugin

    Vite プラグイン (vite-intlayer)

    Viteユーザーはこれらを直接設定することはありません。これらは、vite.config.tswithIntlayer() を呼び出したときに自動的に設定されます。intlayer.config.tsbuild.purge および build.minify フラグは、追加のプラグイン登録なしに対応する動作を切り替えます。

    内部の Vite プラグイン 同等の動作
    Usage analyzer intlayerPurgeBabelPlugin の分析パスと同じ
    Dictionary prune intlayerPurgeBabelPlugin のJSON書き込みパスと同じ
    Dictionary minify intlayerMinifyBabelPlugin のJSON書き込みパスと同じ
    Babel transform intlayerMinifyBabelPlugin のソースコード名変更 + intlayerOptimizeBabelPlugin と同じ

    SWC プラグイン (@intlayer/swc)

    Next.js ユーザーもこれらを直接設定することはありませんv9.2.1 以降、next.config.tswithIntlayer()build.purgebuild.minify フラグだけを元に、パージ・ミニファイ・インポート書き換えというパイプライン全体を実行します。

    SWC の Wasm プラグインは一度に 1 ファイルしか変換できず、ファイルシステムにアクセスできないため、処理は 2 つに分かれています:

    パス 実行場所 処理内容
    使用状況の解析 + JSON のパージ/ミニファイ Node、withIntlayer() の内部 すべてのコンポーネントのソースファイルを読み取り、.intlayer/**/*.json を書き換え、リネームテーブルを生成します
    ソースの書き換え (content.title.a) @intlayer/swc (Wasm) リネームテーブルをコード内の該当するプロパティアクセスに適用します
    インポートの書き換え (useIntlayer → dict) @intlayer/swc (Wasm) intlayerOptimizeBabelPlugin と同じ

    どの フィールドが未使用か、そして各フィールドが どの エイリアスを受け取るかを決定するには、ファイル横断の状態とファイル I/O が必要です。そのためこの半分は Node 上で実行され、SWC プラグインは生成されたテーブルだけを受け取ります。

    プラットフォーム別の設定

    Next.js

    Next.js はビルドに SWC を使用するため、@intlayer/swc プラグインが必要です。v9.2.1 以降、このパッケージ 1 つでパイプライン全体 — 最適化(インポート書き換え)、パージ、ミニファイ — をカバーします。

    SWCプラグインはNext.jsではまだ実験的であるため、このプラグインはデフォルトではインストールされません。将来的に変更される可能性があります。
    Next.js 16.1.0 が最小バージョンです。 SWC の前方互換な Wasm プラグイン ABI 上に構築された最初のリリースであり、それ以前のリリースはプラグインを拒否します。withIntlayer はプロジェクトの Next.js バージョンを読み取り、16.1.0 未満ではプラグインを登録しません — それらのビルドは引き続き成功し、単にバンドル最適化なしで実行されます。
    bash
    npm install -D @intlayer/swc
    
    Cargo.toml
    [dependencies]
    intlayer-swc-plugin = "*"
    

    View on Crates.io

    インストールされると、Intlayerはプラグインを自動的に検出して使用します。

    パージとミニファイのパス(フィールドの削除とリネーム)には、追加パッケージも babel.config.js も不要です。設定を withIntlayer でラップし、intlayer.config.ts でフラグを有効にしてください:

    next.config.ts
    import { withIntlayer } from "next-intlayer/server";
    import type { NextConfig } from "next";
    
    const nextConfig: NextConfig = {/* あなたの設定 */};
    
    export default withIntlayer(nextConfig);
    
    intlayer.config.ts
    import type { IntlayerConfig } from "intlayer";
    
    const config: IntlayerConfig = {
      build: {
        purge: true, // バンドルされた JSON から未使用のコンテンツフィールドを削除します
        minify: true, // コンテンツフィールドのキーを短いエイリアスにリネームします
      },
    };
    
    export default config;
    

    next build の実行中、withIntlayer はソースを解析し、コンパイル済み辞書を書き換え、生成されたフィールドのリネームテーブルを @intlayer/swc に渡します。プラグインはコード内の該当するプロパティアクセスを更新します。

    withIntlayerSync ではなく、非同期の withIntlayer を使用してください。同期版は解析パイプラインを実行しないため、パージとミニファイは効果がありません。
    パージとミニファイは next build 時にのみ実行されます — 最適化パイプラインは next dev 中は無効です。
    互換アダプターの呼び出し元が設定されている場合(swcExtraCallers@intlayer/next-intl@intlayer/react-i18next などの互換パッケージが設定します)も無効になります。これらの呼び出し箇所は使用状況アナライザーから見えないため、パージするとコードがまだ読んでいるフィールドを削除してしまいます。インポートの書き換えは有効なままです。

    それ以前のバージョン(9.2.1 より前) では @intlayer/babel と、intlayerPurgeBabelPlugin および intlayerMinifyBabelPlugin を宣言する babel.config.js が必要でした。このファイルはもう不要で、削除できます。

    Vite

    Viteは @intlayer/babel プラグインを使用し、これは vite-intlayer の依存関係として含まれています。インポートの書き換え、パージ、ミニファイの完全な最適化パイプラインはデフォルトで有効になっており、追加のプラグイン登録は必要ありません。

    intlayer.config.ts で対応するフラグを設定して、パージとミニファイを有効にします。

    intlayer.config.ts
    import type { IntlayerConfig } from "intlayer";
    
    const config: IntlayerConfig = {
      build: {
        purge: true, // バンドルされたJSONから未使用のコンテンツフィールドを削除
        minify: true, // コンテンツフィールドキーを短いエイリアスに変更
      },
    };
    
    export default config;
    

    Webpack (および Babel を使用した Next.js)

    @intlayer/babel をインストールします。

    bash
    npm install -D @intlayer/babel
    

    正しい順序で4つすべてのプラグインを babel.config.js に追加します。

    babel.config.js
    const {
      intlayerExtractBabelPlugin,
      intlayerPurgeBabelPlugin,
      intlayerMinifyBabelPlugin,
      intlayerOptimizeBabelPlugin,
      getExtractPluginOptions,
      getPurgePluginOptions,
      getMinifyPluginOptions,
      getOptimizePluginOptions,
    } = require("@intlayer/babel");
    
    module.exports = {
      plugins: [
        // Extract: .content.ts ファイルをコンパイル → .intlayer/**/*.json
        [intlayerExtractBabelPlugin, getExtractPluginOptions()],
    
        // Purge: .intlayer/**/*.json から未使用のフィールドを削除
        //    (intlayer.config.ts の build.purge フラグを読み取ります)
        [intlayerPurgeBabelPlugin, getPurgePluginOptions()],
    
        // Minify: JSON + ソースコード内のフィールドキーの名前を変更
        //    (intlayer.config.ts の build.minify フラグを読み取ります)
        [intlayerMinifyBabelPlugin, getMinifyPluginOptions()],
    
        // Optimize: useIntlayer('key') → useDictionary(hash) に書き換え
        //    辞書キーを消去するため、最後に配置する必要があります。
        [intlayerOptimizeBabelPlugin, getOptimizePluginOptions()],
      ],
    };
    

    設定

    intlayer.config.tsbuild プロパティを介して、Intlayerがバンドルを最適化する方法を制御できます。

    intlayer.config.ts
    import { Locales, type IntlayerConfig } from "intlayer";
    
    const config: IntlayerConfig = {
      internationalization: {
        locales: [Locales.ENGLISH, Locales.JAPANESE],
        defaultLocale: Locales.ENGLISH,
      },
      dictionary: {
        importMode: "dynamic",
      },
      build: {
        // ビルド時にuseIntlayer()の呼び出しを直接の辞書インポートに置き換えます。
        // undefined = auto (本番環境で有効), true = 常に有効, false = 常に無効。
        optimize: undefined,
    
        // コンパイルされた辞書内のコンテンツフィールドキーを短いアルファベットのエイリアスに
        // 名前変更します(例:title → a)。JSONサイズを縮小します。optimizeが必要です。
        minify: true,
    
        // ソースコードでアクセスされないコンテンツフィールドを削除します。
        // optimizeが必要です。
        purge: true,
      },
    };
    
    export default config;
    
    ほとんどの場合、optimize にはデフォルト値(undefined)を保持することをお勧めします。
    すべてのオプションについては、設定リファレンスを参照してください:設定

    ビルドオプション

    プロパティ デフォルト 説明
    optimize boolean / undefined undefined インポートの書き換えパスを有効にします。undefined = 本番ビルドでのみアクティブになります。false の場合、purgeとminifyも無効になります。
    minify boolean false コンパイルされたJSONファイル内のコンテンツフィールドキーを短いアルファベットのエイリアスに名前変更します。ソースコード内の対応するプロパティアクセスも書き換えます。optimizefalse の場合は効果がありません。
    purge boolean false コンパイルされたJSONファイルから、ソースコードで静的にアクセスされないコンテンツフィールドを削除します。optimizefalse の場合は効果がありません。

    最小化(Minification - フィールドキーの名前変更)

    build.minify はJavaScriptバンドルを最小化しません。それはバンドラーが処理します。代わりに、ユーザー定義の各コンテンツフィールドキーを短いアルファベットのエイリアスに置き換えることで、コンパイルされた辞書のJSONファイルを縮小します。

    plaintext
    // 最小化前
    { "title": "こんにちは", "subtitle": "世界" }
    
    // 最小化後
    { "a": "こんにちは", "b": "世界" }
    

    ソースコード内のすべてのプロパティアクセスにも同じ名前の変更が適用されるため、コンパイルされた出力では content.titlecontent.a になります。実行時の動作は同じです。

    intlayer.config.ts
    import type { IntlayerConfig } from "intlayer";
    
    const config: IntlayerConfig = {
      build: {
        minify: true,
      },
    };
    
    export default config;
    
    optimizefalse の場合、最小化はスキップされます。editor.enabledtrue の場合でも実行されますが、フィールドのリネーム処理は行われません — ビジュアルエディタは keyPath で編集内容を解決するため、元のフィールド名を維持する必要があります。
    Next.js では、@intlayer/swc がインストールされていない、または読み込めない場合(16.1.0 未満の Next.js)にもミニファイはスキップされます。ソース側のアクセスを書き換えるのはこのプラグインなので、これなしで辞書をリネームすると、コードが存在しないフィールド名を読むことになります。
    importMode: 'fetch' 経由で読み込まれた辞書の場合も最小化はスキップされます。これは、そのJSONが元のフィールド名を使用してリモートAPIから提供されるためであり、クライアント側のキーの名前を変更するとサーバー/クライアントの規約が壊れるためです。

    パージ(Purging - 未使用フィールドの削除)

    build.purge は、ソースコード内で実際にアクセスされているコンテンツフィールドを分析し、コンパイルされたJSONファイルから他のすべてのフィールドを削除します。

    intlayer.config.ts
    import type { IntlayerConfig } from "intlayer";
    
    const config: IntlayerConfig = {
      build: {
        purge: true,
      },
    };
    
    export default config;
    

    例: 5つのフィールドがあり、そのうち2つだけが使用されている辞書:

    plaintext
    // パージ前
    { "title": "…", "subtitle": "…", "cta": "…", "footer": "…", "badge": "…" }
    
    // パージ後(ソース内でアクセスされるのはtitleとsubtitleのみ)
    { "title": "…", "subtitle": "…" }
    
    optimizefalse の場合、パージはスキップされます。editor.enabledtrue の場合でも有効なままです — パージされたフィールドはどのコンポーネントからも読み取られないため、エディタがそれを描画することはありません。Next.js では、さらに @intlayer/swc が利用できない場合、および互換アダプターの呼び出し元が設定されている場合にもスキップされます。
    ソースファイルが解析できない場合、または useIntlayer の結果が変数に割り当てられ、静的アナライザーが追跡できない方法(例:オブジェクトへのスプレッド、分割代入せずにプロップとして渡すなど)で渡された場合も、パージは保守的にスキップされます。このような場合は、完全な辞書が保持されます。

    インポートモード

    複数のページとロケールを含む大規模なアプリケーションの場合、JSONはバンドルサイズの大部分を占める可能性があります。Intlayerでは、importMode オプションを使用して辞書の読み込み方法を制御できます。

    グローバル定義

    インポートモードは、intlayer.config.ts ファイルでグローバルに定義できます。

    intlayer.config.ts
    import type { IntlayerConfig } from "intlayer";
    
    const config: IntlayerConfig = {
      dictionary: {
        importMode: "dynamic", // デフォルトは 'static'
      },
    };
    
    export default config;
    

    辞書ごとの定義

    個々の辞書のインポートモードを、その .content.{{ts|tsx|js|jsx|mjs|cjs|json|jsonc|json5|md|mdx|yaml|yml}} ファイルで上書きすることができます。

    ts
    import { type Dictionary, t } from "intlayer";
    
    const appContent: Dictionary = {
      key: "app",
      importMode: "dynamic", // デフォルトのインポートモードを上書き
      content: {
        // ...
      },
    };
    
    export default appContent;
    
    プロパティ デフォルト 説明
    importMode 'static', 'dynamic', 'fetch' 'static' 非推奨: 代わりに dictionary.importMode を使用してください。辞書の読み込み方法を決定します(以下を参照)。

    importMode 設定は、辞書のコンテンツをコンポーネントに注入する方法を決定します。これを intlayer.config.tsdictionary オブジェクトでグローバルに定義するか、辞書ごとの .content.ts ファイルで上書きすることができます。

    1. 静的モード (default)

    静的モードでは、Intlayerは useIntlayeruseDictionary に置き換え、辞書をJavaScriptバンドルに直接注入します。

    • メリット: 即時レンダリング(同期)、ハイドレーション時の追加のネットワークリクエストなし。
    • デメリット: バンドルには、その特定のコンポーネントで利用可能なすべての言語の翻訳が含まれます。
    • 最適なケース: シングルページアプリケーション(SPA)。

    変換されたコードの例:

    tsx
    // あなたのコード
    const content = useIntlayer("my-key");
    
    // 変換後の最適化されたコードの図解(Static)
    // これは図解のみを目的としており、最適化の理由により実際のコードは異なります
    const content = useDictionary({
      key: "my-key",
      content: {
        nodeType: "translation",
        translation: {
          en: "My title",
          ja: "私のタイトル",
        },
      },
    });
    

    2. 動的モード

    動的モードでは、Intlayerは useIntlayeruseDictionaryAsync に置き換えます。これにより、import()(Suspenseのようなメカニズム)を使用して、現在のロケールのJSONを特別に遅延読み込みします。

    • メリット: ロケールレベルでのツリーシェイキング。 英語バージョンを表示しているユーザーは、英語の辞書のみをダウンロードします。日本語の辞書は読み込まれません。
    • デメリット: ハイドレーション中にコンポーネントごとにネットワークリクエスト(アセットの取得)をトリガーします。
    • 最適なケース: バンドルサイズが重要な大規模なテキストブロック、記事、または多くの言語をサポートするアプリケーション。

    変換されたコードの例:

    tsx
    // あなたのコード
    const content = useIntlayer("my-key");
    
    // 変換後の最適化されたコードの図解(Dynamic)
    // これは図解のみを目的としており、最適化の理由により実際のコードは異なります
    const content = useDictionaryAsync({
      en: () =>
        import(".intlayer/dynamic_dictionary/my-key/en.json").then(
          (mod) => mod.default
        ),
      ja: () =>
        import(".intlayer/dynamic_dictionary/my-key/ja.json").then(
          (mod) => mod.default
        ),
    });
    
    importMode: 'dynamic' を使用する場合、1つのページに useIntlayer を使用するコンポーネントが100個あると、ブラウザは100回の個別のフェッチを試みます。このリクエストの「ウォーターフォール」を避けるために、アトムコンポーネントごとに1つではなく、より少ない数の .content ファイル(例:ページのセクションごとに1つの辞書)にコンテンツをグループ化してください。同じキーを使用する複数の .content ファイルを使用することもできます。Intlayerはそれらを1つの辞書に統合します。

    3. Fetchモード

    動的モードと同様に動作しますが、最初に Intlayer Live Sync API から辞書を取得しようとします。API呼び出しが失敗した場合、またはコンテンツがライブ更新の対象としてマークされていない場合は、動的インポートにフォールバックします。

    変換されたコードの例:

    tsx
    // あなたのコード
    const content = useIntlayer("my-key");
    
    // 最適化されたコードの図解(Fetch)
    const content = useDictionaryAsync({
      en: () =>
        fetch("https://intlayer.my-domain.com/dictionary/my-key/en").then((res) =>
          res.json()
        ),
      ja: () =>
        fetch("https://intlayer.my-domain.com/dictionary/my-key/ja").then((res) =>
          res.json()
        ),
    });
    
    詳細については、CMSのドキュメントを参照してください:CMS
    fetchモードでは、JSONが元のフィールド名を使用してリモートAPIから提供されるため、purgeとminifyは適用されません。

    要約: 静的 vs 動的

    機能 静的モード 動的モード
    JSバンドルサイズ より大きい(コンポーネントの全言語が含まれる) 最小(コードのみ、コンテンツなし)
    初期読み込み 即時(コンテンツはバンドル内) わずかな遅延(JSONを取得)
    ネットワークリクエスト 追加のリクエストなし 辞書キーごとに1つのリクエスト
    ツリーシェイキング コンポーネントレベル コンポーネントレベル + ロケールレベル
    最適なユースケース UIコンポーネント、小規模アプリ テキストが多いページ、多言語