コードを保存しても画面が更新されありません。ターミナルは動いているように見えるのに、アプリは数分前の状態のまま。Rorkで生成したExpoアプリを触っていて、こうした瞬間に何度も出会いました。そして毎回、原因が違うのです。あるときはキャッシュ、あるときは node_modules、またあるときは Watchman のファイル監視数の上限でした。
Metro bundlerと Fast Refresh の不具合は、React Native開発において避けて通れない壁です。ここでは私自身が現場で遭遇した症状と、どの順番で何をチェックすれば最短で復旧できるかを整理します。表面的な「キャッシュを消せば直る」で終わらせず、なぜそれが起きているのかまで踏み込みます。
最初に確認すべき3つのシグナル
症状を見てすぐに rm -rf node_modules を叩きたくなる気持ちは分かります。ただ、その前に30秒だけ、以下の3つを確認すると原因の切り分けが一気に進みます。
1つ目はターミナルのログです。 Metroが BUNDLE や Hot update のメッセージを出しているか。出ているなら、Metro自体は動いており、問題はデバイス側(アプリ・シミュレータ)の受信側にあります。出ていないなら、Metro側で何かが詰まっています。
2つ目は変更を加えたファイルのパスです。 プロジェクトルート配下にあるか? node_modules 内のファイルを編集していないか? シンボリックリンク経由のファイルを変更していないか? Metroの監視対象外のファイルを触っても Fast Refresh は動きません。これは意外と見落とされます。
3つ目はエラーメッセージの有無です。 Fast Refresh が発動しようとして失敗している場合、エディタのコンソールか Metro のターミナルに警告が出ていることが多いです。「Fast Refresh had to perform a full reload」というメッセージを見かけたら、それはコンポーネントの構造に問題があるサインです(後述します)。
Metro bundlerが固まる代表的な原因
Metroが応答しなくなる原因は、私の経験上この5つに集約されます。
キャッシュの肥大化と破損
Metroは .expo/ や /tmp/metro-* 配下に中間ファイルをキャッシュします。これが破損すると、コードを書き換えても古いバンドルが返されます。Rork Max で生成されたコードを Xcode 側で編集したあとにこの症状が出やすいです。プロジェクト外からファイルが変更されるとMetroの差分検知が混乱するためです。
Watchmanの監視ファイル数上限
macOSで開発している場合、Watchmanがファイル変更を監視していますが、デフォルトでは1プロセスあたり8,192ファイルまでです。モノレポや大きな依存グラフを持つプロジェクトでは簡単に上限を超え、以降の変更検知が無視されます。
node_modulesと package-lock.json の不整合
npm install と yarn install を混在させたり、異なるバージョンのNode.jsで依存関係を入れ直したりすると、node_modules の中身とロックファイルがずれます。Metroはこの状態でも一応動こうとしますが、特定のモジュール解決で無限ループや停止が起きます。
Expo SDKのバージョン不一致
app.json の Expo SDKバージョンと、package.json の expo パッケージのバージョンがずれると、Metroは起動するもののバンドル中に固まることがあります。Rorkでアプリをアップデートしたあと、ローカル側だけ古いバージョンのままになっていると発生しやすい現象です。
ポート占有
Metroはデフォルトで 8081 ポートを使います。他のプロセスが占有していると、Metroは別ポートに逃げるか起動に失敗します。しかしデバイス側のアプリは依然として 8081 に接続しに行くため、「Metroは動いているのに繋がらない」状態になります。
症状別の診断フローチャート
症状A:ファイルを保存しても画面が変わらない
まず Metro のログを確認します。BUNDLE のメッセージが出ているなら、デバイス側の接続問題です。アプリを手動でリロード(iOSシミュレータなら ⌘R、Androidなら端末を振るかメニュー)してみてください。リロードで最新コードが反映されるなら、Fast Refresh だけが壊れています。この場合、コンポーネントの構造を疑います。
BUNDLE が出ていないなら、Metroがファイル変更を検知できていません。Watchmanを再起動するのが最も早い解決策です。
# Watchmanの状態をリセット
watchman watch-del-all
watchman shutdown-server
# Metroも再起動
npx expo start --clear期待する出力: Starting Metro Bundler の後に BUNDLE ./App.tsx のメッセージが出ること。ここで止まる場合は次のステップへ。
症状B:Metroが「Loading dependency graph...」から進まない
これは依存関係の解決中にMetroが詰まっている状態です。原因はほぼ node_modules の不整合です。安全な順番でクリーンアップします。
# Step 1: Metroとシミュレータを閉じる
# Ctrl+C でMetro停止、シミュレータアプリも終了
# Step 2: キャッシュを削除
rm -rf node_modules
rm -rf .expo
rm -f package-lock.json
# Step 3: npmキャッシュも念のためクリア
npm cache verify
# Step 4: 再インストール
npm install
# Step 5: キャッシュクリアして起動
npx expo start --clear期待する出力: Metro waiting on exp://192.168.x.x:8081 のQRコード表示。
症状C:「Fast Refresh had to perform a full reload」が頻発する
これはコードの書き方に問題があるサインです。Fast Refresh は関数コンポーネント(React Function Component)の「純粋な更新」しか扱えません。以下のパターンは全リロードを誘発します。
- ファイルのトップレベルで副作用を持つコードを実行している(
console.logを直書きする等) export defaultが複数ある- クラスコンポーネントと関数コンポーネントを同じファイルに混在させている
- 匿名関数(
export default () => {})をデフォルトエクスポートしている
匿名関数のデフォルトエクスポートは特に見落としがちです。Rorkが生成するコードにもたまに現れるので、以下のように名前付きにするだけで改善することが多いです。
// ❌ Fast Refresh が効きにくい
export default () => {
return <View>...</View>
}
// ✅ 関数に名前を付けると Fast Refresh が安定する
export default function HomeScreen() {
return <View>...</View>
}「完全リセット」コマンド集(安全な実行順)
それでも直らない時のための、最終手段としてのフルリセット手順です。左から順に実行し、各ステップで症状が直るか確認してください。いきなり最後まで実行する必要はありません。
#!/bin/bash
# rork-metro-reset.sh — 安全な順番でリセットする
# Level 1: Metroキャッシュのみクリア(最も軽い)
npx expo start --clear
# Level 2: Watchmanもリセット
watchman watch-del-all && watchman shutdown-server
npx expo start --clear
# Level 3: .expo と node_modules を削除
rm -rf .expo node_modules
npm install
npx expo start --clear
# Level 4: ロックファイルも削除(依存関係を再解決)
rm -rf .expo node_modules package-lock.json
npm install
npx expo start --clear
# Level 5: iOSシミュレータの開発者メニューから「Erase All Content and Settings」
# Level 6: Xcodeの DerivedData を削除
rm -rf ~/Library/Developer/Xcode/DerivedData/*Level 3以降はビルド時間が長くなるので、Level 1か2で直ることを祈りながら進めるのが効率的です。実際、私の環境ではLevel 2までで8割の症状が解消します。
再発防止のためのプロジェクト設定
一時的に直っても、同じ症状が毎週繰り返すようなら、プロジェクト構成を見直す価値があります。
.gitignore に .expo/ を含めているか を確認してください。チームで開発している場合、誰かのキャッシュディレクトリが誤ってコミットされると、他のメンバーの環境でMetroが混乱します。
Node.jsのバージョンを固定する のも効果的です。プロジェクトルートに .nvmrc を置き、チーム全員が同じNode.jsバージョンを使うようにします。バージョン差異による node_modules の微妙な違いは、Metroの停止原因として意外と多いです。
# .nvmrc
20.11.0
Watchmanの監視上限を上げる のも、大きなプロジェクトでは有効です。macOSの場合、以下のコマンドで監視可能なファイル数を増やせます。
echo "kern.maxfiles=524288" | sudo tee -a /etc/sysctl.conf
echo "kern.maxfilesperproc=524288" | sudo tee -a /etc/sysctl.conf再起動後に有効になります。Expoの大規模アプリや、react-native-reanimated などファイル数の多いライブラリを多用するプロジェクトでは、ここを上げておくと安心です。
ビルドエラーの周辺トピックは Expo依存関係エラーの修正ガイド や ビルドエラー完全ガイド にまとめてあります。Metro以外の層で詰まっている場合はそちらも併せて確認してください。
今日からできる次の一歩
Metroの問題は「再現性が低いからこそ厄介」という性質があります。次に症状が出た時のために、今日のうちに以下を準備しておくことをおすすめします。
プロジェクトルートに scripts/reset.sh を作り、上記のLevel 1〜3までを書いておきましょう。症状が出た瞬間に ./scripts/reset.sh を叩ける状態にしておくと、思考を中断せずに作業に戻れます。開発のリズムを守ることは、直接的な生産性以上に、集中力の持続に効きます。私自身、この一手間を加えてから、Metro関連のストレスが明らかに減りました。
Rorkで生成されたコードの読み解きにも役立つ一冊です。