◉RORK LABEN
●GPT6.1 — Rork のモデルメニューに GPT-6.1 Sol が加わりました(9月29日)。GPT-6 Sol と同価格で、1M トークンの文脈を読みます●10/12 — React Native 0.88.x の正式リリース予定まで残り6日。Expo SDK 58 安定版はその後で、Expo Go の更新で SDK 57 のサポートが落ちます●CRYPTO — expo-crypto の digest() が、TypeScript 上は ArrayBuffer を受けるのに Android のネイティブ側は TypedArray を求める、という Issue が出ています(修正 PR あり)●NEW — モデルメニューの入れ替わりを3種類に分けて、クレジットの行き先を台帳に残します●SDK58 — SDK 58 の安定版の日付は、昨日の確認時点ではまだ出ていませんでした●SHEET — expo-ui の BottomSheet で presentationBackground の素材が平らで不透明に描かれる、という報告が出ています●GPT6.1 — Rork のモデルメニューに GPT-6.1 Sol が加わりました(9月29日)。GPT-6 Sol と同価格で、1M トークンの文脈を読みます●10/12 — React Native 0.88.x の正式リリース予定まで残り6日。Expo SDK 58 安定版はその後で、Expo Go の更新で SDK 57 のサポートが落ちます●CRYPTO — expo-crypto の digest() が、TypeScript 上は ArrayBuffer を受けるのに Android のネイティブ側は TypedArray を求める、という Issue が出ています(修正 PR あり)●NEW — モデルメニューの入れ替わりを3種類に分けて、クレジットの行き先を台帳に残します●SDK58 — SDK 58 の安定版の日付は、昨日の確認時点ではまだ出ていませんでした●SHEET — expo-ui の BottomSheet で presentationBackground の素材が平らで不透明に描かれる、という報告が出ています
記事一覧/開発ツール
⬡ 開発ツール/2026-06-22上級

壊れたキャッシュで毎回起動時に落ちるアプリ——ユーザーが自力で抜け出せる「セーフモード起動」を設計する

永続化したキャッシュが壊れて起動のたびに同じ場所で落ちると、ユーザーには再インストールしか残りません。アプリ自身が早期クラッシュの連続を数え、対話可能になって初めて起動を確定し、危険な状態だけを段階的にリセットするセーフモードを、Expo(React Native)の実装で設計します。

Rork575Expo212React Native238クラッシュ対策4MMKV6設計10

✦ プレミアム記事

個人開発で複数のアプリを運用していると、ごくたまに「特定の端末だけ、起動した瞬間に落ちて二度と開けない」という報告が届きます。私自身、永続化していた設定の一部が壊れたレコードになり、起動時の復元処理がそこで例外を投げて、以後は何度開いても同じ場所で落ち続ける状態に遭遇したことがあります。

厄介なのは、この状態に陥ったユーザーには手段がほとんど残らないことです。アプリは開けない、設定画面にもたどり着けない、つまりアンインストールして入れ直す以外にできることがありません。起動ループはそのまま離脱率に直結します。そして再インストールは、レビュー欄でいちばん辛辣な一言につながります。

ここで扱いたいのは、壊れた状態を後から手で直すことではありません。アプリ自身が「自分は起動のたびに早期に落ちている」と気づき、危険な状態だけを段階的に捨てて立ち上がり直す——そんなセーフモード起動の設計を扱います。

なぜ ErrorBoundary や Crashlytics では抜け出せないのか

まず、既存の備えがこの問題のどこに効かないかを整理しておきます。

ErrorBoundary は強力ですが、守れるのは React のレンダーツリーの内側だけです。起動ループの多くは、プロバイダが永続ストアを復元している最中や、ネイティブ側のモジュール初期化で起きます。ツリーがマウントされる前に落ちれば、境界は捕まえる対象を持ちません。React の例外捕捉の基本は未処理の Promise まで取りこぼさない ErrorBoundary の設計で扱っていますが、それでも「マウント前のクラッシュ」は守備範囲の外です。

Crashlytics はクラッシュを記録してくれますが、記録は事後です。ユーザーの端末でループが止まるわけではありません。iOS の 0x8badf00d ウォッチドッグ終了のように OS がメインスレッドの停滞で殺してくるケースとも違い、ここで問題なのは「コードは正しく動いているのに、与えられた永続データが壊れている」点です。コードを直しても、すでに壊れた状態を持っている端末は救われません。

つまり必要なのは、観測でも捕捉でもなく、端末側で自走する回復ロジックです。起動直後の白画面・クラッシュの切り分けを一歩進め、切り分けをアプリ自身にやらせる、と考えると分かりやすいかもしれません。

設計の核:起動を「未確認」で数え、対話可能になって初めて確定する

仕組みの中心はとても単純です。

起動が始まった瞬間に「未確認の起動」を1つ増やします。そしてアプリが実際に対話可能な状態(最初の画面が描画され、ユーザーが触れる状態)まで到達したら、その未確認カウントをゼロに戻します。これを「起動の確定」と呼ぶことにします。

もしアプリが対話可能になる前に落ちれば、確定は実行されません。未確認カウントは増えたまま残ります。次の起動でまた増え、また落ちれば、カウントは積み上がっていきます。これが連続して一定回数に達したとき、「この端末は起動ループに入っている」と判断します。

import { MMKV } from 'react-native-mmkv'
 
const store = new MMKV({ id: 'boot-guard' })
const KEY_PENDING = 'boot.pending'      // 未確認の連続起動回数
const KEY_LAST = 'boot.lastStartAt'     // 直近の起動開始時刻
const FAILED_BOOT_THRESHOLD = 3         // この回数の連続失敗でセーフモード
const RECENT_WINDOW_MS = 30_000         // この間隔を超える起動は「連続」とみなさない
 
export type BootDecision = { safeMode: boolean; failedBoots: number }
 
// プロバイダを一切マウントする前に、エントリの先頭で呼ぶ
export function beginBoot(): BootDecision {
  const now = Date.now()
  const lastStart = store.getNumber(KEY_LAST) ?? 0
  let pending = store.getNumber(KEY_PENDING) ?? 0
 
  // 速い連続でなければ起動ループではない。数え直す
  if (now - lastStart > RECENT_WINDOW_MS) pending = 0
 
  store.set(KEY_PENDING, pending + 1)
  store.set(KEY_LAST, now)
 
  // この起動を始める前に、すでに閾値ぶん落ちているか
  return { safeMode: pending >= FAILED_BOOT_THRESHOLD, failedBoots: pending }
}
 
// 対話可能になったら呼ぶ。これで連続失敗カウントが消える
export function confirmBoot() {
  store.set(KEY_PENDING, 0)
}

RECENT_WINDOW_MS を挟んでいるのは、誤検知を避けるためです。ユーザーがアプリを開いてすぐ閉じ、数日後にまた開いた——というのはクラッシュループではありません。連続して短時間に起動が積み上がったときだけをループとみなします。

✦

ここまでお読みいただきありがとうございます。

この記事の続きを読む

この先には、実装コードやベンチマーク結果など、実務でお役に立てる内容をご用意しています。このサイトは広告を掲載しておらず、サーバーや開発にかかる費用はメンバーの皆様のご支援で成り立っています。もしお役に立てていましたら、ご支援いただけますと大変ありがたいです。

この記事で得られること
✦ErrorBoundary でも Crashlytics でも救えない「起動ループ」を、アプリ自身に気づかせて自己回復させる設計
✦起動を未確認で数え、対話可能になって初めて確定する仕組みと、なぜ同期ストレージ(MMKV)でないと数えられないのか
✦被害範囲を最小にする段階的リセットの梯子と、ユーザー作成データを絶対に触らないための原則
Stripe による安全な決済 · いつでもキャンセル可能
✦

この記事を購入する

この先の内容をすべてお読みいただけます。一度のご購入で、いつでも何度でもアクセスできます。このサイトは広告を掲載しておらず、皆さまのご支援がサーバー費用などの運営を支えています。

または
メンバーシップなら全記事が読み放題 →
シェア

お読みいただきありがとうございます

Rork Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • ✦コピー&ペーストで使える実装コード付き
  • ✦毎日新しい上級ガイドを追加
  • ✦¥580/月 または ¥2,480 の永久アクセス
メンバーシップを見る →

関連記事

⬡ 開発ツール2026-09-04
EAS の secret は「アプリに入れない」設定ではありません — 接頭辞と可視性を別々に決める
EXPO_PUBLIC_ の接頭辞は成果物に入るかを決め、EAS の可視性は誰が読めるかを決めます。二つを重ねたときに OTA 更新でだけ値が消える筋道と、出荷前に自分の手で確かめる手順をまとめました。
⬡ 開発ツール2026-08-22
一括置換は全ファイル成功しました。壊れたのは、消さなかった行のほうです
生成コードに一括置換をかけると、一致した行ではなく隣の行が壊れます。運用中のプロジェクトで実際に起きた破損と、置換の不変条件を検査する20行のガードスクリプトをまとめました。
⬡ 開発ツール2026-08-04
名前が20文字ちょうどでも保存できない — 文字数の数え方を1か所に決めるまでの実測
絵文字や合成文字が入ると、クライアントとサーバーで文字数が食い違います。4つの数え方をNode v22で実測し、切り詰め2,000件の破損率75.6%・UTF-8往復での置換文字化36.1%という結果から、カウントの正本を1か所に置く設計へ至るまでをまとめました。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます