開発中のアプリは、シミュレーターでも Expo Go でも問題なく開きます。ところが提出用に Release でビルドした iOS だけが、アイコンをタップした瞬間に落ちるのです。画面が一度も出ないので、ログを見るまで何が原因なのか見当がつきませんでした。
2026 年 9 月に Expo の GitHub に上がった報告が、まさにこの形でした。expo-router を使ったアプリで、app.json に scheme を書いていないと、iOS の Release ビルドだけが起動直後にクラッシュする、というものです。Rork で作ったプロジェクトを書き出して自分の Mac でビルドしている方は、一度ご自身の app.json を開いて確かめていただく価値があります。
報告されている症状と、一覧に出ない理由
報告者の最小再現リポジトリでは、次の組み合わせでした。
- 版は expo 57.0.24、expo-router 57.0.22、expo-linking 57.0.10、react-native 0.86.3 です
- app.json に
schemeが書かれていません - iOS の Release ビルドは、起動直後に SIGABRT で落ちます
- 同じアプリでも、Debug の iOS と、Android の Debug・Release は正常に起動します
もう一つ、見落としやすい点があります。報告者の環境では expo-doctor が 21 項目すべて通っていました。つまり「診断が緑だから大丈夫」とは言えない種類の不具合です。私自身、診断が通っているとそこで安心してしまいがちなので、この点は書き留めておきます。
なぜ Debug と Android は無事なのか
原因は、Expo の検証 bot が追跡した内容と、報告者の調査が一致しています。順に辿ります。
expo-router は iOS で起動するとき、ルート URL を作るために Linking.createURL('/') を同期的に呼びます。この関数の内側で、expo-linking が scheme を解決します。scheme が一つもないとき、挙動は実行モードで分かれます。
- Debug(
__DEV__が true)では、警告をコンソールに出すだけで続行します - Release では、
Cannot make a deep link into a standalone app with no custom scheme definedという例外を投げます
iOS ではこの例外が、起動処理の途中で捕まらずに JS の致命的エラーとして報告され、Release では赤い画面が出ないため、そのままネイティブのクラッシュになります。Android では同じ呼び出しが Promise の then の中にあり、例外が「拒否された Promise」に化けるだけで、アプリは落ちません。同じコードなのに、iOS だけが同期的に呼ばれている——これが「iOS の Release だけ」の正体です。
直し方は二つあります
1. scheme を足す
もっとも確実で、アプリを変えずに済む手当てです。
{
"expo": {
"name": "my-app",
"slug": "my-app",
"scheme": "myapp"
}
}Expo の bot の検証では、scheme を入れたビルドは冷間起動 3 回中 3 回とも開きました。scheme は小文字の英数字で、他のアプリと被りにくい文字列にしておくと安心です。
2. 修正済みの expo-router へ上げる
この件は expo-router 側で修正されました。CHANGELOG の 58.0.11 に「scheme がないとき、本番で起動時にクラッシュする問題を修正」という項目があります。修正の中身は、createURL が投げた例外を捕まえて '/' に戻す、という小さなものです。scheme があるアプリは従来と同じ経路を通ります。
注意点が一つあります。私が確認できたのは 58.0.11 の記載までで、SDK 57 系に同じ修正が入った版があるかは確認できていません。SDK 57 のまま出すアプリは、版の上げ下げを待たずに、まず 1 の scheme を足すほうが確実だと考えています。
scheme を外したいのが意図的な場合
報告者が scheme を入れていなかったのは、うっかりではありませんでした。カスタム URL scheme を OS に登録しなければ、他のアプリから myapp:// で自分のアプリを開かれる入口が減ります。そのための引き算だった、と書かれています。
ここで一つ整理しておきたいことがあります。Expo の bot が添えていた注記のとおり、scheme は「他のアプリが URL で自分のアプリを開けるようにする」ためのものです。保護したい画面を守るのは scheme の有無ではなく、画面側の認証チェックです。scheme を外しても、守りたい画面の認証を省いてよい理由にはなりません。逆に、scheme を足したからといって、認証が弱くなるわけでもありません。
個人開発で壁紙のようなアプリを出しているぶんには、ディープリンクで開かれて困る画面はほとんどありません。一方、課金や個人情報に近い画面を持つアプリでは、scheme を足したうえで、その画面が直接開かれても認証で弾かれることを確かめておく、という順序が現実的だと思います。
scheme は入口の数、認証は扉の鍵。 入口を減らす判断と、鍵をかける判断は、別々に済ませます。
提出前に見つける 2 つの確認
この種の不具合は、Debug で何時間触っても出てきません。提出前の確認に、次の二つを足すことにしました。
確認 1: 解決後の設定に scheme があるか
app.json だけでなく、app.config.js で動的に組み立てている場合も拾えるよう、解決後の設定を見ます。
npx expo config --json | node -e '
let s = "";
process.stdin.on("data", d => s += d).on("end", () => {
const j = JSON.parse(s);
const c = j.exp ?? j; // 出力の形が違っても拾えるように
const sc = [].concat(c.scheme ?? []).filter(Boolean);
console.log(sc.length ? "scheme: " + sc.join(", ") : "NO SCHEME");
process.exit(sc.length ? 0 : 1); // CI や提出前スクリプトで落とせる
});'NO SCHEME と出たら、意図して外したのかを一度だけ自分に問います。意図して外したなら、expo-router を修正済みの版にしてあるかを確認し、そうでなければ scheme を足します。
確認 2: Release 構成を冷間起動する
もう一つは、Release 構成で、アプリを完全に終了してから起動し直して開くかを見ることです。
npx expo run:ios --configuration ReleaseExpo の bot が使った手順は「アプリを終了して冷間起動し、10 秒待って状態を記録する」を 3 回繰り返すものでした。起動直後に落ちる不具合は、1 回では偶然通ることがあるので、私も 3 回見るようにしています。
ここまでの落とし穴
expo-doctorが通っていても、この不具合は見つかりません- Debug と Android の Release が動くことは、iOS の Release が動く根拠になりません
- scheme を足すときは、同じ文字列を他の自作アプリで使い回していないかを確かめてください
Release だけ落ちるという症状は、似た形の原因が他にもあります。名前の文字列で引いていた資源が縮小で消える件は debug では動くのに、release の起動だけ「見つからない」で落ちましたに書きました。また、ログを黙らせる設定が失敗の理由を隠していた件は exit code 0 で止まるビルドにまとめています。実機での確認が、ストア版との差をどこまで埋めてくれるかは Rork Companion で動いた画面を、私はまだ完成とは呼びませんで整理しました。
次のビルドの前に、まず上の確認 1 を一度だけ実行してみてください。出力が一行出るだけですが、提出の夜に慌てずに済む一行だと、いまは感じています。