skip to content

What does a React Native debug build need to install on a physical iPhone, and how does it then find the Metro dev server?

level: middleimportance: should knowfreq 34%

answer

  1. signing first, network second
  2. pick a Team for app and Tests targets
  3. build writes the Mac's IP into ip.txt
  4. same Wi-Fi as the Mac
  5. unreachable: falls back to embedded bundle

basics

~20 s

Xcode must sign it with a development Team on the app and Tests targets. At build time the bundling script writes the Mac's IP into the app; the app loads JavaScript from Metro there if reachable, otherwise the bundle embedded at build time.

solid answer

~50 s

Two separate steps. First, iOS installs only signed apps, so in Xcode I register the device via Product → Destination and pick my Apple Developer Team in the Signing settings of the main target and the Tests target; then Cmd+R runs it on the phone. Second, the **Bundle React Native code and images** phase, for Debug builds on a device, writes the Mac's IP into the app as `ip.txt` and also embeds a JavaScript bundle. At launch the app checks for Metro at that IP; if it answers, the app loads fresh code and Fast Refresh works, which needs the phone and the Mac on the same network. If it doesn't, the app silently falls back to the embedded bundle, so edits never appear. The fix is the same Wi-Fi, a Personal Hotspot on locked-down networks, or a rebuild when the Mac's IP changed.

go deeper

for a junior

Recall that the phone needs a development Team set in Xcode's signing settings and the same Wi-Fi network as the Mac running Metro.

for a middle

Explain the build-time IP embedding, the check for a running dev server at launch, and the fallback to the embedded bundle.

for a senior

Diagnose the silent case where edits don't appear, and choose between the same network, a Personal Hotspot, a rebuild or skipping debug bundling to make failures visible.

for a principal

Decide how the team gets reliable on-device iteration, weighing office network policy, shared test devices and build-time costs.

## Two separate problems Running a React Native debug build on your own iPhone involves two independent steps, and interview answers often blur them: 1. **Getting the app onto the phone**: iOS only installs apps signed for that device, so Xcode needs a development **Team** to sign with. 2. **Getting the phone to your Metro dev server**: the debug app must find your computer on the network to load fresh JavaScript. ## Step 1: Team signing for development The React Native docs describe the setup: - Connect the iPhone by cable and open the project's `.xcworkspace` (CocoaPods projects) in Xcode. - The first time, choose the device under **Product → Destination** so Xcode registers it for development. - Select the project in the navigator, then the **main app target**, and in its **Signing** settings pick your Apple Developer account or team from the **Team** dropdown. - Repeat the same for the **Tests** target, which also has to sign. - Build and run with **Cmd+R**; the device appears as the run destination. This is *development* signing, handled by Xcode for your own devices. Distribution certificates, provisioning for the App Store and sharing certificates across a team are different topics. In an Expo project, `npx expo run:ios --device` builds for a connected device and lets you pick it from a list, but the same signing requirement applies underneath. ## Step 2: how the debug app finds Metro On a **physical device**, the Xcode build phase **Bundle React Native code and images** (the `react-native-xcode.sh` script) does two things in the Debug configuration that it skips for the Simulator: - It **detects your Mac's IP address** at build time and writes it into the app as `ip.txt`. - It **bundles the JavaScript into the app** as well, printing *"Bundling for physical device"*. For the Simulator it skips bundling, because the Simulator can always reach Metro on the Mac. At launch, React Native's bundle URL provider reads `ip.txt`, falls back to `localhost` if it is missing, and checks whether a dev server is actually running there. If one is, the app loads its JavaScript from Metro. If not, it **falls back to the bundle embedded at build time**. | Situation | What loads | What you notice | |---|---|---| | Phone and Mac on the same network, Metro running | Fresh bundle from Metro | Fast Refresh works | | Different network, captive portal or Metro stopped | Embedded bundle from the last build | App runs, but your edits never appear | | Mac's IP changed since the build | Embedded bundle | Same as above until you rebuild | | Debug bundling skipped (`SKIP_BUNDLING`) and Metro unreachable | Nothing to fall back to | An error instead of the app | ## When the connection fails The docs' troubleshooting list: 1. Make sure the Mac and the phone are on the **same** Wi-Fi network. 2. Check the IP the build embedded: in Xcode's **Report navigator**, open the last build log and search for `IP=`; it should match the Mac's current address. 3. On guest or captive-portal networks that block device-to-device traffic, use the phone's **Personal Hotspot**, or share the Mac's connection to the phone over USB. Rebuilding after a network change refreshes `ip.txt`. ## Android is different Android debug builds on a device do none of this: they assume `localhost` and rely on `adb reverse` over USB, or on a host and port typed into the Dev Menu's **Dev Settings**. On iOS no manual host setting is needed in the normal case, because the build embeds the Mac's address, and there is no `adb` equivalent to forward ports. Knowing which platform uses which mechanism answers most "my phone can't see Metro" questions. ## Why the silent fallback matters The fallback is convenient, since the app still opens away from your desk, but it is a classic source of confusion: a developer edits a screen, sees no change on the phone and starts debugging code that was never loaded. The Dev Menu and a quick visible edit reveal it; the real fix is restoring the network path. The React Native docs also suggest skipping debug bundling with `SKIP_BUNDLING` to save build time, which removes the fallback and turns the silent failure into a visible one.

  • You change a screen, but the app on your iPhone still shows the old version and no error. What is happening?
    The app couldn't reach Metro at the IP embedded when it was built, so it fell back to the JavaScript bundle embedded in the debug build. Check that the phone and the Mac share a network, that Metro is running, and that the IP in the build log still matches the Mac's; rebuild if it changed.
  • Why doesn't a Debug build on the iOS Simulator need ip.txt or an embedded bundle?
    The Simulator runs on the Mac itself, so localhost already reaches Metro. The bundling script therefore skips IP detection for simulator builds and skips bundling in Debug for the Simulator, since Metro serves the code; the app's host lookup falls back to localhost when ip.txt is absent.

saying these in an interview costs you the question

  • An iPhone finds Metro the same way the Simulator does, through localhost.
  • Signing is only needed for App Store builds, not for your own phone.
  • If Metro is unreachable, the app always shows a red error screen.
  • adb reverse makes an iPhone reach Metro over USB.
  • The Mac's IP is read at app launch, so network changes need no rebuild.