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
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 clean macOS build pipeline such as NoMac when the repository contains a conventional native ios/ project that you need to build and ship.
Do not push only the ios/ directory. A React Native iOS build also needs the JavaScript application, package.json, a consistent lockfile, and workspace configuration. Push the complete application root.
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 native macOS pipeline |
| 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 an agent-driven build, signing, TestFlight, and store workflow without owning a Mac.
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 a release build.
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
Authenticate from the complete application root:
cd MyApp npx @nomac/cli login
Register npx @nomac/cli mcp with the coding agent. The agent should push from the directory containing both package.json and ios/.
# Claude Code example claude mcp add nomac -- npx @nomac/cli mcp
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. Push the complete app and smoke-build iOS
Call push_project from the app root, then run workflow=smoke. NoMac installs locked JavaScript dependencies, prepares the CocoaPods workspace for conventional projects, and invokes the iOS build on macOS.
Run the React Native type checks and tests. Confirm there is one package manager and that its lockfile is current. Push from the directory containing package.json and ios/. Run a NoMac smoke 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 smoke build passes, start workflow=release. The cloud Mac creates the signed archive and uploads it. Install it through TestFlight 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
Run review_lint, 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. Stage the release, 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.