◉RORK LABJP
●GPT-6.1 SOL — Added to Rork's model menu (09-29) with Low, Medium and High effort; included on Pro and Max●SONNET 5.5 — Claude Sonnet 5.5 is selectable: strong at UI design and faster than Sonnet 5 (09-28)●SONNET 4.5 — Retires on the Claude API on 11/30, 31 days left. If your app calls it, check the migration target●SDK 58 ISSUE — Early reports include Android widgets not refreshing on a schedule and Stripe linking statically●OBSERVE — EAS Observe now records native crashes too (SDK 57.0.21 or later, 10/07)●NEW — My App Open Ad Only Showed When the App…●GPT-6.1 SOL — Added to Rork's model menu (09-29) with Low, Medium and High effort; included on Pro and Max●SONNET 5.5 — Claude Sonnet 5.5 is selectable: strong at UI design and faster than Sonnet 5 (09-28)●SONNET 4.5 — Retires on the Claude API on 11/30, 31 days left. If your app calls it, check the migration target●SDK 58 ISSUE — Early reports include Android widgets not refreshing on a schedule and Stripe linking statically●OBSERVE — EAS Observe now records native crashes too (SDK 57.0.21 or later, 10/07)●NEW — My App Open Ad Only Showed When the App…
Articles/Dev Tools
⬡ Dev Tools/2026-10-30Intermediate

It runs in development but the iOS Release build dies on launch — checking whether a missing scheme is the cause

In an expo-router app, an app.json with no scheme can crash only the iOS Release build at launch. Why Debug and Android survive, the two ways to fix it, how to think about leaving the scheme out on purpose, and a pre-submission check.

Rork611Expo233expo-router4iOS118Release buildTroubleshooting54Deep linking

The app opens fine in the simulator and in Expo Go. Then you build it in Release for submission, tap the icon on an iOS device, and it is gone before a single screen appears. With nothing on screen, there is no hint about where to start.

A report filed on Expo's GitHub in September 2026 has exactly this shape: in an expo-router app, if app.json has no scheme, only the iOS Release build crashes right at launch. If you export a Rork project and build it on your own Mac, it is worth opening your app.json once to check.

What the report describes, and why no diagnostic flags it

The minimal reproduction used this combination:

  • expo 57.0.24, expo-router 57.0.22, expo-linking 57.0.10, react-native 0.86.3
  • No scheme in app.json
  • The iOS Release build aborts with SIGABRT right after launch
  • The same app starts normally in iOS Debug, and in Android Debug and Release

One detail is easy to miss. In the reporter's environment, expo-doctor passed all 21 checks. This is not the kind of failure a green diagnostic rules out, and as an indie developer who tends to relax once the checks are green, I wanted that written down.

Why Debug and Android survive

The explanation from Expo's verification bot and the reporter's own trace agree, so I'll follow them in order.

On iOS, expo-router calls Linking.createURL('/') synchronously at launch to build the root URL. Inside it, expo-linking resolves the scheme. When there is none, the behavior depends on the mode:

  • In Debug (__DEV__ is true), it only prints a warning and carries on
  • In Release, it throws Cannot make a deep link into a standalone app with no custom scheme defined

On iOS, that exception is not caught during startup and is reported as a fatal JS error. Release has no red screen, so it becomes a real native crash. On Android the same call sits inside a Promise then, so the exception turns into a rejected Promise and the app stays up. Same logic, but only iOS calls it synchronously — that is the whole "only iOS Release" puzzle.

Two ways to fix it

1. Add a scheme

This is the most dependable fix and leaves the rest of the app alone.

{
  "expo": {
    "name": "my-app",
    "slug": "my-app",
    "scheme": "myapp"
  }
}

In the bot's verification, the build with a scheme opened on 3 of 3 cold launches. Use lowercase letters and digits, and something unlikely to collide with another app.

2. Move to a fixed expo-router

The fix landed in expo-router. Its CHANGELOG lists, under 58.0.11, "Fix a crash on launch in production when the app has no scheme." The change is small: it catches the error from createURL and falls back to '/'. Apps that have a scheme take the same path as before.

One caveat: I could only confirm the 58.0.11 entry. I have not confirmed whether a release on the SDK 57 line carries the same fix. If you are shipping on SDK 57, adding the scheme from option 1 is the safer move while you wait.

When leaving the scheme out is deliberate

The reporter did not omit the scheme by accident. Without a custom URL scheme registered with the OS, other apps have fewer ways to open yours with myapp://, and that was the point.

It helps to separate two things here. As the bot's note put it, a scheme only lets other apps open your app by URL. What protects a sensitive screen is the authentication check on that screen, not the presence or absence of a scheme. Removing the scheme is not a reason to skip auth, and adding one does not weaken it.

For a wallpaper-style app I run, there are few screens that deep links could open to anyone's harm. For an app with purchases or anything close to personal data, the practical order is: add the scheme, then confirm that opening those screens directly still hits the auth check.

The scheme is the number of doors; authentication is the lock. Decide how many doors to have and whether to lock them as two separate decisions.

Two checks before you submit

This kind of bug never shows up in hours of Debug testing. I'm adding two checks to my own pre-submission routine.

Check 1: does the resolved config have a scheme?

To catch apps that build their config in app.config.js as well as plain app.json, look at the resolved config.

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;               // tolerate either output shape
  const sc = [].concat(c.scheme ?? []).filter(Boolean);
  console.log(sc.length ? "scheme: " + sc.join(", ") : "NO SCHEME");
  process.exit(sc.length ? 0 : 1);    // lets CI or a submit script fail
});'

If it prints NO SCHEME, ask yourself once whether that was intentional. If it was, confirm you are on a fixed expo-router; if not, add the scheme.

Check 2: cold-launch the Release configuration

Run the Release configuration, fully quit the app, and open it again:

npx expo run:ios --configuration Release

The bot's procedure was to terminate the app, cold-launch it, wait 10 seconds, and record the state, three times. A launch crash can slip past once by chance, so I look three times too.

Pitfalls

  • A passing expo-doctor does not catch this
  • Debug working, or Android Release working, is not evidence that iOS Release works
  • If you add a scheme, make sure you are not reusing the same string in another app of your own

Other causes produce the same "only Release dies" symptom. I wrote up a resource looked up by name and removed by shrinking in debug worked, release failed with "not found" at launch, and a flag that silenced the log and hid the real failure in a build that stops with exit code 0. How far a device check gets you toward the store build is in a screen that worked in Rork Companion is not finished yet.

Before your next build, run Check 1 once. It prints a single line, but it is a line that spares you a panicked submission night — or so it feels to me now.

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 →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

⬡ Dev Tools2026-09-21
A build that stops with exit code 0 — the flag that silenced the log, and the check that let an empty file through
Two reports landed in the same week: eas build --local stopping with exit code 0 but produced no further output, and a zero-byte stub that never got embedded. Both came down to settings that reduce output. Here is how to audit your own scripts.
⬡ Dev Tools2026-10-15
When EXPO_PUBLIC_ Values Read Fine in Dev but Come Back undefined in Production: Telling the Spellings Apart
The API URL reads fine while developing and turns undefined once the app is built. The cause was how process.env was written. I lined up babel-preset-expo's output for dev and production and tabulated which spellings get replaced and which are left behind.
⬡ Dev Tools2026-09-05
Your Widget Extension Can Submit a BGTaskScheduler Request. It Just Cannot Register the Handler
Calling BGTaskScheduler from a widget extension compiles, submits, and returns true — and then nothing runs. Here is why registration belongs to the host app only, and how I moved refresh ownership back where it belongs across my wallpaper apps.