React Nativeでアプリを開発していると、npx react-native start を実行した直後にターミナルへ表示される「Metro waiting on…」というメッセージを目にします。このMetroこそ、React Nativeアプリのビルドプロセスを支えるJavaScriptバンドラーです。

Metro Bundlerは、Meta(旧Facebook)が開発・メンテナンスしているReact Native専用のバンドラーで、公式サイトでは “The JavaScript bundler for React Native” と定義されています(出典: Metro公式)。開発中のJavaScriptコードとアセットを1つのバンドルファイルにまとめ、iOS・Androidのネイティブ環境で実行可能な形式へ変換する役割を担っています。

Metroが担う3つの役割

Metro Bundlerは単なるファイル結合ツールではなく、React Native開発のワークフロー全体を支える基盤です。主に次の3つの機能を提供しています。

  • バンドル生成: 複数のJavaScriptファイルと依存パッケージを解析し、1つのバンドルファイルへまとめます
  • 開発サーバー: ローカルHTTPサーバーとしてデバイス・エミュレータへバンドルを配信し、ソースコードの変更をリアルタイムに反映します
  • アセット管理: 画像やフォントなどの静的アセットの解決とプラットフォームごとの最適化を処理します

アーキテクチャ:3フェーズでバンドルを生成する仕組み

Metroのビルドパイプラインは、Resolution → Transformation → Serialization の3つのフェーズで構成されています(出典: Metro Concepts)。

Resolution(依存解決)

エントリーポイント(通常は index.js)を起点に、importrequire で参照されるすべてのモジュールを探索し、依存関係グラフ(Dependency Graph)を構築するフェーズです。

index.js
├── App.js
│   ├── ./screens/HomeScreen.js
│   ├── ./components/Header.js
│   └── react-navigation
└── react-native

metro.config.jsresolver セクションでファイル拡張子やモジュール検索パスをカスタマイズできます。

Transformation(コード変換)

依存グラフ上の各モジュールを、ターゲットプラットフォームで実行可能な形式へトランスパイルするフェーズです。内部ではBabelが使われ、JSX構文やTypeScript、最新のECMAScript構文をES5互換コードへ変換します。

MetroはこのフェーズをマシンのCPUコア数に応じて並列処理するため、大規模プロジェクトでも高速にビルドが完了します。maxWorkers オプションで並列度を制御できます。

Serialization(バンドル出力)

変換が完了した全モジュールを結合し、最終的なJavaScriptバンドルファイルとして出力するフェーズです。Webバンドラーがブラウザ向けにファイルを生成するのと異なり、Metroはネイティブアプリに埋め込めるバイナリ形式のバンドルを出力できます。

この特性により、React Nativeコンポーネントをネイティブアプリの一部として動的にロードする構成が可能になります。

Metroの高速性を支える3つの設計思想

Metro公式が掲げる設計原則は次の3点です。

設計原則内容
Fast(高速)サブセカンド(1秒未満)のリロードサイクルを実現。差分ビルドとキャッシュにより、変更部分だけを再処理します
Scalable(拡張性)数千モジュール規模のプロジェクトでもパフォーマンスが劣化しない設計です
Integrated(統合性)React Nativeプロジェクトに対してゼロコンフィグで動作し、追加設定なしで即座に使えます

React Native 0.79以降ではDeferred Hashing機能が導入され、起動時のファイルチェックを遅延実行することで、特に大規模プロジェクトやモノレポ構成において起動速度が3倍以上向上しています(出典: React Native 0.79 公式ブログ)。

Hot Module Replacement(HMR)の仕組み

Metro開発サーバーはファイルシステムの変更を常時監視しています。開発者がコードを保存すると、以下のプロセスが自動的に実行されます。

  1. 変更検知: ファイルシステムウォッチャー(watchman)が保存を検知します
  2. 差分ビルド: 変更されたモジュールだけをTransformationフェーズで再変換します
  3. 差分配信: WebSocket経由で変更差分をデバイス・エミュレータに送信します
  4. 状態保持更新: アプリの状態(state)を維持したまま、変更部分だけを置き換えます

React Nativeでは「Fast Refresh」という名称でHMR機能が提供されており、関数コンポーネントのコード変更時にstateを保持したまま即座にUIが更新されます。

metro.config.js の設定ガイド

基本テンプレート

React Nativeプロジェクトの metro.config.js は、以下のテンプレートが標準です。

const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config');

/**
 * Metro configuration
 * https://metrobundler.dev/docs/configuration
 *
 * @type {import('metro-config').MetroConfig}
 */
const config = {};

module.exports = mergeConfig(getDefaultConfig(__dirname), config);

getDefaultConfig(__dirname) でReact Nativeに必要なデフォルト設定を取得し、mergeConfig でカスタム設定をマージするパターンです。

主要な設定オプション

設定は大きく5つのセクションに分かれています(出典: Metro Configuration)。

General Options(全般)

const config = {
  projectRoot: __dirname,       // プロジェクトルート
  watchFolders: ['../shared'],  // 監視対象の追加ディレクトリ
  maxWorkers: 4,                // 並列ワーカー数
  resetCache: false,            // 起動時キャッシュリセット
};

Resolver Options(モジュール解決)

const config = {
  resolver: {
    sourceExts: ['js', 'jsx', 'json', 'ts', 'tsx'],  // デフォルト
    assetExts: ['png', 'jpg', 'gif', 'webp'],
    platforms: ['ios', 'android', 'windows', 'web'],
    resolverMainFields: ['browser', 'main'],
    extraNodeModules: {
      '@app': path.resolve(__dirname, 'src'),
    },
  },
};

Transformer Options(コード変換)

const config = {
  transformer: {
    babelTransformerPath: require.resolve('react-native-svg-transformer'),
    minifierPath: 'metro-minify-terser',
    enableBabelRuntime: true,
  },
};

Server Options(開発サーバー)

const config = {
  server: {
    port: 8081,            // デフォルトポート
    forwardClientLogs: true,
  },
};

よくある設定例:SVGファイルのインポート対応

React NativeプロジェクトでSVGファイルをコンポーネントとして使うには、以下のように設定します。

const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config');
const defaultConfig = getDefaultConfig(__dirname);

const config = {
  transformer: {
    babelTransformerPath: require.resolve('react-native-svg-transformer'),
  },
  resolver: {
    assetExts: defaultConfig.resolver.assetExts.filter(ext => ext !== 'svg'),
    sourceExts: [...defaultConfig.resolver.sourceExts, 'svg'],
  },
};

module.exports = mergeConfig(defaultConfig, config);

Expoプロジェクトでの Metro設定

Expoを使っている場合、@expo/metro-config パッケージから getDefaultConfig をインポートします(出典: Expo Metro設定)。

const {getDefaultConfig} = require('@expo/metro-config');

const config = getDefaultConfig(__dirname);

// カスタム設定例: .cjs ファイルのサポート追加
config.resolver.sourceExts.push('cjs');

module.exports = config;

Expoでは mergeConfig を使わず、取得したデフォルト設定オブジェクトを直接変更するパターンが推奨されています。

キャッシュのクリア方法

Metroはビルド高速化のためにTransformationの結果をローカルにキャッシュしています。ライブラリの更新後やビルドの不整合が発生した場合、キャッシュのクリアが有効です。

コマンド一覧

状況コマンド
Metro起動時にキャッシュリセットnpx react-native start --reset-cache
Expo環境npx expo start --clear
npmキャッシュも含めた全クリアwatchman watch-del-all && rm -rf node_modules && npm install && npx react-native start --reset-cache
Gradleキャッシュ(Android)cd android && ./gradlew clean
Podキャッシュ(iOS)cd ios && pod cache clean --all && pod install

キャッシュクリアが必要になる代表的なケース

  • npm installyarn add でパッケージを追加・更新した直後
  • metro.config.js を編集した後
  • ブランチを切り替えた際に依存関係が変わった場合
  • ビルドは成功するが実行時に古いコードが参照される場合
  • Unable to resolve module エラーが解消しない場合

Metro・webpack・Viteの比較

React NativeではMetroが標準バンドラーですが、Webフロントエンド開発で主流のwebpackやViteとの違いを整理しておくと、Metroの位置づけがより明確になります。

比較項目MetrowebpackVite
主な用途React NativeモバイルアプリWebアプリ全般モダンWebアプリ
開発元MetaTobias Koppers / コミュニティEvan You(Vue.js作者)
初回ビルド速度高速(差分ハッシュ)低速(全体バンドル)非常に高速(ESM活用)
HMR速度Fast Refresh(高速)中程度(500ms〜1.6s)非常に高速(10〜20ms)
設定の複雑さゼロコンフィグ高い(loaderとplugin)低い(合理的なデフォルト)
出力形式ネイティブアプリ埋め込みバンドルブラウザ向けJS/CSSブラウザ向けJS/CSS
プラグインエコシステム限定的非常に豊富拡大中
Tree Shaking限定的対応対応(Rollup経由)

MetroはReact Nativeのネイティブバイナリ出力とプラットフォーム別のモジュール解決に特化している点が、汎用Webバンドラーとの最大の違いです。

セキュリティ上の注意:CVE-2025-11953(Metro4Shell)

2025年11月に公開されたCVE-2025-11953(通称: Metro4Shell)は、Metro開発サーバーに存在するOSコマンドインジェクション脆弱性です。CVSS 9.8(Critical)と評価されています(出典: GitHub Advisory)。2025年12月以降、実環境での悪用が確認されており、2026年2月にはCISA(米国サイバーセキュリティ・インフラストラクチャセキュリティ庁)のKEVカタログにも追加されました(出典: VulnCheck)。

影響範囲

  • 対象パッケージ: @react-native-community/cli バージョン 4.8.0 以降(バージョン 17.0.0以降はさらに深刻度が増大)
  • 脆弱なエンドポイント: /open-url(POSTリクエストで任意のOSコマンドが実行可能。特にWindows環境では完全なシェルコマンド実行が可能)
  • リスク: Metroがデフォルトですべてのネットワークインターフェースにバインドするため、同一ネットワーク上の攻撃者からリモートでコマンドを実行される恐れがあります

対策

  1. パッケージ更新: @react-native-community/cli-server-api を修正済みバージョン(18.0.1、19.1.2、または 20.0.0 以上)に更新します
  2. ホスト制限: 開発サーバー起動時に --host 127.0.0.1 を指定し、localhostのみにバインドします
  3. ネットワーク隔離: 開発マシンを信頼できないネットワークに接続した状態でMetroを起動しないようにします
# ホストをlocalhostに制限して起動
npx react-native start --host 127.0.0.1

よくあるトラブルと解決方法

「Unable to resolve module」エラー

モジュールの依存解決に失敗した場合に表示されます。

# 1. node_modules の再インストール
rm -rf node_modules && npm install

# 2. Metroキャッシュのクリア
npx react-native start --reset-cache

metro.config.jsextraNodeModules を設定することで、カスタムパスのモジュールも解決可能です。

ポート8081が使用中

Metro開発サーバーのデフォルトポート8081が別プロセスで使われている場合の対処法です。

# 別ポートで起動
npx react-native start --port 8082

または metro.config.js でポートを固定します。

const config = {
  server: {
    port: 8082,
  },
};

モノレポ構成での設定

モノレポのようにプロジェクトルート外にソースコードがある場合、watchFolders で監視対象を追加します。

const path = require('path');

const config = {
  watchFolders: [
    path.resolve(__dirname, '../../packages/shared'),
  ],
  resolver: {
    extraNodeModules: {
      '@shared': path.resolve(__dirname, '../../packages/shared'),
    },
  },
};

まとめ

Metro BundlerはReact Nativeアプリ開発の根幹を担うJavaScriptバンドラーです。Resolution・Transformation・Serializationの3フェーズで依存解決からコード変換、バンドル出力までを高速に処理し、Fast Refreshによるリアルタイム開発体験を提供します。

metro.config.js を適切に設定することで、SVGインポートやモノレポ対応など柔軟なカスタマイズが可能です。一方でCVE-2025-11953のようなセキュリティリスクも報告されているため、開発環境のパッケージバージョンとネットワーク設定には注意が必要です。