React Native iOS Development on Windows and Linux · NoMac

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.

September 9, 2026 ·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 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

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

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

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

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.