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"))'
hostUri に xxxxxx-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を別途用意する。