Build a React Native iOS app on Windows or Linux
Choose the right route for Expo or bare React Native, prepare reproducible iOS dependencies, and create signed TestFlight builds without owning a Mac.
· 10 min read
Need a Mac for this? NoMac gives your agent full macOS with Xcode for $0.80 an hour, billed per minute. Start now →
You can develop most of a React Native app on Windows or Linux, but the native iOS target still needs Xcode on macOS. JavaScript or TypeScript, Metro, shared tests, and Android can run locally. Native iOS modules, CocoaPods, the archive, code signing, and TestFlight must pass through a Mac.
The best release route depends on the project. Use Expo and EAS for a managed Expo app. Use a cloud Mac such as NoMac when the repository contains a conventional native ios/ project that you need to build and ship.
Do not send only the ios/ directory. A React Native iOS build also needs the JavaScript application, package.json, a consistent lockfile, and workspace configuration. Put the complete application root on the Mac.
First choose Expo or a native iOS project
| Project | Use when | Recommended iOS path |
|---|---|---|
| Managed Expo | Expo owns native generation and your libraries fit its workflow | Expo Go for development, EAS Build/Submit for release |
| Expo with checked-in native folders | You use prebuild but maintain ios/ changes | EAS or a cloud Mac |
| Bare React Native | You own the Xcode project and native modules | Build complete repository on macOS |
| Existing native app with RN module | React Native is embedded in a larger iOS app | Project-specific Mac workflow |
The React Native team's current getting-started guide recommends using a framework such as Expo for most new apps. Expo Application Services already understands Expo's project lifecycle, so using EAS for managed Expo is normally the simplest choice.
Choose NoMac for a conventional bare React Native app when the complete native iOS source is in the repository and you want your agent to build, sign, and send to TestFlight on a real Mac without owning one. An Expo app can take the same route after npx expo prebuild -p ios creates its ios/ folder.
What Windows and Linux can verify
- ·JavaScript and TypeScript type checks, linting, and unit tests.
- ·Metro bundling and most shared application logic.
- ·Android builds and emulator behavior.
- ·API, backend, and web components.
- ·Source control, dependency resolution, and agent-driven edits.
They cannot prove that a native module supports the current iOS SDK, that CocoaPods resolves, that the workspace and scheme are correct, or that the app archives. React Native's official device guide uses Xcode on macOS for the physical iOS device and signing steps.
Prepare a reproducible repository
A conventional project should include:
MyApp/
├── package.json
├── package-lock.json # or exactly one yarn/pnpm lockfile
├── .nvmrc # optional exact Node version
├── index.js
├── src/
└── ios/
├── Podfile
├── Podfile.lock # commit when CocoaPods is used
├── MyApp.xcodeproj/
└── MyApp.xcworkspace/ # generated/reconstructed for Pods- ·Keep exactly one JavaScript package-manager choice at the install root.
- ·Commit the matching lockfile and keep it consistent with
package.json. - ·Pin modern Yarn or pnpm with an exact
packageManagerversion. - ·Use an exact Node version in
.nvmrcor.node-versionwhen the project requires one. - ·Commit
Podfile.lockand use the CocoaPods workspace. - ·Commit shared schemes and all native module configuration.
Monorepos need the workspace definition at the same root as the lockfile. Arbitrary install scripts and missing package roots are not reproducible inputs. If the iOS project refers to React Native but the JavaScript root is absent, fail before spending Mac time.
Set the iOS identity early
The Xcode project's bundle identifier must match the App Store Connect record. Confirm the deployment target, product name, version, build number, capabilities, privacy purpose strings, and app icon. Native modules can add entitlements or privacy requirements; review them rather than assuming all JavaScript packages are platform-neutral.
Connect the project to a cloud Mac
Install the CLI and authenticate from the complete application root:
cd MyApp npm install -g @nomac/cli nomac login
Register npx @nomac/cli mcp with the coding agent. The agent starts a Mac with start_mac and clones the repository into ~/drive/src/MyApp, or runs nomac sync . from the directory containing both package.json and ios/.
# Claude Code example claude mcp add nomac -- npx @nomac/cli mcp
The Mac has Xcode, Node, Ruby and CocoaPods installed. ~/drive is kept between sessions, with npm and CocoaPods caches in ~/drive/.cache, so the next session does not start from an empty install.
The React Native build loop
1. Run local checks
Run the project's type check, linter, unit tests, and bundle checks. Test Android or Expo behavior if applicable, but record that it is shared-behavior evidence rather than an iOS pass.
2. Put the complete app on the Mac and compile iOS
Start a Mac and put the app root in ~/drive/src. On the Mac, install the locked JavaScript dependencies, run pod install in ios/, and build the workspace for the Simulator. That unsigned build is the compile check; it needs no Apple account.
npm ci cd ios && pod install xcodebuild -workspace MyApp.xcworkspace -scheme MyApp \ -destination 'platform=iOS Simulator,name=iPhone 17 Pro' build | xcbeautify
Run the React Native type checks and tests. Confirm there is one package manager and that its lockfile is current. Start a NoMac Mac, sync the directory containing package.json and ios/, then run npm ci, pod install and a Simulator build. If it fails, classify the first actionable JavaScript, CocoaPods, native-module, or Xcode error before editing.
3. Read native failures precisely
- ·A Metro or bundle error usually belongs to JavaScript configuration or dependencies.
- ·A CocoaPods resolution error belongs to Pod constraints, lockfiles, or deployment targets.
- ·A missing header or module may mean a native package version is incompatible.
- ·A scheme or workspace error belongs to committed Xcode configuration.
- ·A signing error belongs to bundle identity, capabilities, certificates, or profiles.
4. Release to TestFlight
After the compile check passes, the agent calls set_up_signing (CLI: nomac sign). It registers the bundle ID, makes the App Store profiles, and returns the exact archive and export commands. Run them, then send the .ipa with send_to_testflight (CLI: nomac testflight). Apple processes it, usually in 5 to 15 minutes, and it appears in TestFlight for your internal testers. Install it and test startup, bundle loading, navigation, deep links, native permissions, push notifications, offline behavior, and every native module used by the main flow.
Common mistakes
- ·Sending only
ios/and omitting the JavaScript workspace. - ·Keeping multiple conflicting npm, Yarn, or pnpm lockfiles.
- ·Relying on an uncommitted
node_modulesor Pods directory. - ·Using a floating Node or package-manager version that changes between builds.
- ·Assuming an Android pass proves iOS native modules work.
- ·Using NoMac for a managed Expo app when EAS already fits it better.
Publish the React Native app
Inspect every SDK's data collection and required privacy manifest, prepare screenshots from the TestFlight build, and provide a working review account when login is required. Run review_lint on the App Store record, then stage the release with publish, verify the exact build number, and require human confirmation for final submission.
Follow the 2026 App Store publishing guide for the store sequence. The framework changes how the source is prepared, not Apple's requirement for a signed, reviewed iOS binary.