Expo ngrok ネットワーク

Expo で開発中のアプリを実機のExpo Goで開こうとして、開発マシンと実機を同じWi-Fiに繋いだのに Expo Go のHome画面の「Development Servers」に出てこないことがある。原因の多くはアクセスポイントのクライアント分離(AP isolation)で、同じSSIDに繋がっている端末同士が直接通信できないよう遮断されている。設定を変えられないポケットWi-Fiや公衆Wi-Fiではよく起きる。--tunnel でngrok経由に切り替えるとローカルネットワークを迂回できて解決する。

症状の切り分け

実機と開発マシンが同じSSIDに繋がっているのに、Expo GoのHome画面の「Development Servers」欄が空のまま。npx expo start の画面には Metro waiting on exp://192.168.x.x:8081 と出ているのに実機からアクセスできない。

ローカルネットワークが原因かは、expo start の表示にある IP アドレス(192.168.x.x)に対して実機のブラウザで http://192.168.x.x:8081 を開いてみると分かる。応答しないなら実機から開発マシンへHTTPで到達できていない=AP isolationか、ファイアウォールが8081を遮断している。

AP isolationを疑う目安は次のとおり。

  • ポケットWi-Fiのデフォルト設定(機種によっては初期値で有効)
  • ホテル・カフェ・コワーキングのゲストWi-Fi
  • ゲスト用SSIDとして分けているルーターのゲスト側

本来のオフィスWi-Fiでも管理者が分離を有効にしていることがあるので、SSIDだけでは判定できない。実機ブラウザから開発マシンのHTTPが叩けなければ、原因を掘るより先に迂回する。

tunnel modeへの切り替え

Expoは --tunnel オプションでngrokに乗せてくれる。実機はインターネット経由でngrokのURLを叩き、ngrokがローカルの8081に転送する形になるので、同じWi-Fiに繋がっている必要すらなくなる(実機がLTEでもよい)。

npm install --save-dev @expo/ngrok
npx expo start --tunnel

初回はngrokのバイナリをダウンロードするので少し時間がかかる。起動後は exp://xxxxxx-anonymous-8081.exp.direct のようなURLが表示され、これをQRにしたものを実機Expo Goで読むと繋がる。

非対話モードでURLが表示されない時

スクリプト経由で expo start --tunnel を起動していると、対話UIが省略されて肝心のtunnel URLが標準出力に出ないことがある。この場合はローカルのManifest APIから直接拾える。

curl -s http://localhost:8081/manifest | python3 -c \
  'import sys,json; d=json.load(sys.stdin); print(d.get("extra",{}).get("expoGo",{}).get("developer",{}).get("tool")); print(d.get("hostUri"))'

hostUrixxxxxx-anonymous-8081.exp.direct の形で出ていれば、exp:// プレフィックスを付けたものが実機用URLになる。python3 を使いたくなければ jq.hostUri を取り出しても同じ。

実機へのQR化は、抽出した exp://... をQRコード生成APIに投げるだけで済む。https://api.qrserver.com/v1/create-qr-code/?size=320x320&data=exp%3A%2F%2F... の形式が手軽で、HTMLに埋め込んで開発マシンで表示すれば実機のカメラで読める。

tunnelの副作用

tunnelを常用するとホットリロードのレイテンシが増える。ngrokのサーバを経由する分だけ、ローカルLANで直結する時よりバンドル配信も更新反映も遅い。AP isolationを避けるための緊急回避策と割り切って、開発を続ける環境が確保できたら直結に戻したほうがよい。

また、@expo/ngrok 経由の無料枠は接続数や帯域に上限がある。長時間の共同開発でチーム全員がtunnelから繋ぐような使い方は想定されていないので、そういう場合は開発マシンから直接届くWi-Fiを別途用意する。