RORK LABJP
MAX — Rork Max builds native Swift apps instead of React Native, supporting iPhone, iPad, Watch, TV, Vision Pro, and iMessageNATIVE — It unlocks native capabilities: AR/LiDAR, Metal 3D games, Dynamic Island, Live Activities, HealthKit, and Core MLCORE — Standard Rork generates iOS/Android apps with React Native (Expo), taking you from plain English to the app storesFUNDING — Rork raised $2.8M from a16z (Andreessen Horowitz)GROWTH — The platform now sees 743,000 monthly visits with 85% growthPRICING — Free to start, with paid plans from $25/monthMAX — Rork Max builds native Swift apps instead of React Native, supporting iPhone, iPad, Watch, TV, Vision Pro, and iMessageNATIVE — It unlocks native capabilities: AR/LiDAR, Metal 3D games, Dynamic Island, Live Activities, HealthKit, and Core MLCORE — Standard Rork generates iOS/Android apps with React Native (Expo), taking you from plain English to the app storesFUNDING — Rork raised $2.8M from a16z (Andreessen Horowitz)GROWTH — The platform now sees 743,000 monthly visits with 85% growthPRICING — Free to start, with paid plans from $25/month
Articles/Dev Tools
Dev Tools/2026-06-22Advanced

When a Poisoned Cache Crashes Your App on Every Launch — Designing a Safe-Mode Boot Your Users Can Escape On Their Own

When a persisted cache goes bad and the app crashes at the same spot on every launch, the only option left to the user is to reinstall. This article designs a safe-mode boot for Expo (React Native): the app counts its own early crashes, confirms a launch only once it becomes interactive, and resets just the dangerous state in graduated steps.

Rork440Expo93React Native175Crash ResilienceMMKV6Architecture12

Premium Article

Running several apps on my own as an indie developer, I occasionally get a report that reads like a dead end: on one particular device, the app crashes the instant it launches and never opens again. I have hit this myself. A piece of persisted settings had turned into a corrupt record, the restore step on startup threw an exception right there, and from then on the app died at the same spot no matter how many times it was reopened.

What makes it brutal is how few options the user has left. The app won't open, the settings screen is unreachable, so there is nothing to do but uninstall and reinstall. And a reinstall is exactly the kind of moment that produces the harshest line in a review.

This article is about not fixing that broken state by hand after the fact, but designing a safe-mode boot: the app itself recognizes that it keeps crashing early, throws away only the dangerous state in graduated steps, and stands back up.

Why ErrorBoundary and Crashlytics Can't Get You Out

Let me first place where the usual defenses fall short on this specific problem.

ErrorBoundary is powerful, but it only protects the inside of the React render tree. Most boot loops happen while a provider is rehydrating a persisted store, or during native module initialization. If the crash lands before the tree mounts, the boundary has nothing to catch. The fundamentals of catching exceptions in React — down to unhandled promises — are covered in designing an ErrorBoundary that doesn't drop unhandled promises, but a crash before mount is still out of its reach.

Crashlytics records the crash, but the record is after the fact. It does not stop the loop on the user's device. This is also different from an iOS 0x8badf00d watchdog termination, where the OS kills you for blocking the main thread. Here the code is running correctly — the persisted data it was handed is the thing that is broken. Fixing the code doesn't save a device that already holds the poisoned state.

So what you need is neither observation nor capture, but recovery logic that runs on its own on the device. It helps to think of it as taking the white-screen-versus-crash triage at launch one step further and letting the app perform that triage itself.

The Core Idea: Count a Launch as Unconfirmed Until It Becomes Interactive

The mechanism at the center is simple.

The moment a launch begins, increment an "unconfirmed launch" counter by one. When the app actually reaches an interactive state — the first screen has rendered and the user can touch it — reset that counter to zero. Call this "confirming the launch."

If the app crashes before it becomes interactive, the confirm never runs. The counter stays elevated. The next launch increments it again, and if it crashes again, the count keeps building. When that happens enough times in a row, you conclude that this device has entered a boot loop.

import { MMKV } from 'react-native-mmkv'
 
const store = new MMKV({ id: 'boot-guard' })
const KEY_PENDING = 'boot.pending'      // consecutive unconfirmed launches
const KEY_LAST = 'boot.lastStartAt'     // timestamp of the most recent launch
const FAILED_BOOT_THRESHOLD = 3         // this many consecutive failures -> safe mode
const RECENT_WINDOW_MS = 30_000         // launches farther apart aren't "consecutive"
 
export type BootDecision = { safeMode: boolean; failedBoots: number }
 
// Call at the very top of the entry, before mounting any provider
export function beginBoot(): BootDecision {
  const now = Date.now()
  const lastStart = store.getNumber(KEY_LAST) ?? 0
  let pending = store.getNumber(KEY_PENDING) ?? 0
 
  // Not a quick succession -> not a boot loop. Start counting over.
  if (now - lastStart > RECENT_WINDOW_MS) pending = 0
 
  store.set(KEY_PENDING, pending + 1)
  store.set(KEY_LAST, now)
 
  // Before this launch even starts, have we already failed the threshold?
  return { safeMode: pending >= FAILED_BOOT_THRESHOLD, failedBoots: pending }
}
 
// Call once the app is interactive. This clears the consecutive-failure count.
export function confirmBoot() {
  store.set(KEY_PENDING, 0)
}

The RECENT_WINDOW_MS gate exists to avoid false positives. A user who opens the app, closes it right away, and opens it again days later is not in a crash loop. Only launches that pile up in quick succession count as one.

Thank you for reading this far.

Continue Reading

What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.

WHAT YOU'LL LEARN
Escaping a boot loop that neither ErrorBoundary nor Crashlytics can break — by letting the app notice and recover on its own
Counting a launch as unconfirmed until the app becomes interactive, and why only synchronous storage (MMKV) can record that counter reliably
A graduated reset ladder that minimizes blast radius, with a hard rule never to touch user-created data
Secure payment via Stripe · Cancel anytime

Unlock This Article

Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.

or
Unlock all articles with Membership →
Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • Copy-paste ready implementation code
  • New advanced guides published daily
  • $5/mo or $10 for lifetime access
View Membership →

Related Articles

Dev Tools2026-06-22
Build a Toast System in Your Rork App That Survives Overlaps, Screen Readers, and Notches
When you add toast notifications to a React Native app generated by Rork, the naive version breaks in three places: two toasts overlap, screen readers stay silent, and the text hides under the notch or home indicator. This walks through a root-level queue, animations decoupled from your app tree, AccessibilityInfo announcements, and safe-area placement — all in working code.
Dev Tools2026-06-20
Why Your Rork List Starts Duplicating and Dropping Rows as It Grows — Cursor Pagination and Resilient Refetch State
The naive offset pagination Rork scaffolds for you quietly breaks the moment your list changes underneath the user. Here is how to move to a cursor contract, fold every fetch state into one usePaginatedList hook, and recover failed page loads with exponential backoff — implementation first.
Dev Tools2026-06-20
Bugs Rork Can Fix vs. Bugs You Should Fix Yourself: A Triage Workflow for Exported Code
A practical triage workflow for telling apart the bugs Rork resolves on its own from the ones you should hand-fix in exported React Native/Expo code, with working examples.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links
See all →