← Blog

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

ProjectUse whenRecommended iOS path
Managed ExpoExpo owns native generation and your libraries fit its workflowExpo Go for development, EAS Build/Submit for release
Expo with checked-in native foldersYou use prebuild but maintain ios/ changesEAS or a cloud Mac
Bare React NativeYou own the Xcode project and native modulesBuild complete repository on macOS
Existing native app with RN moduleReact Native is embedded in a larger iOS appProject-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 packageManager version.
  • ·Use an exact Node version in .nvmrc or .node-version when the project requires one.
  • ·Commit Podfile.lock and 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_modules or 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.

Give your agent a Mac in a minute.

Full macOS with Xcode and root, over MCP, CLI, API or SSH. $0.80 an hour, billed per minute. The Mac is deleted at stop; your 20 GB drive is kept for the next one. No Apple account needed to start.

Get started from $10

7-day money-back guarantee on your first purchase. Credit never expires.

Connect your agent →