React Native iOS apps
Bundle your JavaScript into a self-contained simulator build of your React Native iOS app, then zip it with ditto and upload it to Pie. With the JavaScript inside the build, it runs without Metro.
Before you start
- React Native CLI: Version 0.60 or higher
- macOS computer: You can’t build for iOS on Windows or Linux
- Xcode: A current version installed with command line tools
- Node.js: With npm or yarn installed
- CocoaPods: If your project uses native dependencies
Point the build at a test or staging environment. See Prepare mobile builds for the requirements that apply to every platform.
Choose your build method
There are two ways to build:
- Manual bundling: You control the bundling. Best for your first build.
- Automated bundling: Faster, if your project is already set up for it.
Manual bundling method
Install dependencies
Open a terminal in your project root:
# Install JavaScript dependencies
npm install
# or
yarn install
# Install CocoaPods dependencies (if applicable)
cd ios && pod install && cd ..Stop Metro bundler
Make sure Metro isn’t running, so it can’t conflict with the build:
# Kill any running Metro instances
killall -9 nodeOr close any terminal windows running Metro.
Bundle your JavaScript code
Run the bundle command from your project root:
npx react-native bundle \
--entry-file index.js \
--platform ios \
--dev false \
--bundle-output ios/main.jsbundle \
--assets-dest iosYour project might use a different entry file: index.js (standard JavaScript), index.tsx (TypeScript), index.ios.js (platform-specific), or a custom entry defined in package.json or app.json.
This command creates:
- Production JavaScript bundle at
ios/main.jsbundle - Asset files (images, fonts) in the
iosfolder - Hermes bytecode files (
.hbc) if using the Hermes engine - An assets folder, only if your project has images or fonts
Add the bundle to Xcode
Open your iOS project:
open ios/YourProjectName.xcworkspaceUse
.xcodeprojif you don’t use CocoaPods.In Project Navigator (left panel), right-click your project name
Select Add Files to “[YourProjectName]”
Navigate to the
iosdirectorySelect
main.jsbundleand the assets folder, if it existsCheck Copy items if needed, and select your app target under Add to targets
Click Add
To check, select main.jsbundle in Project Navigator and confirm your target is ticked under Target Membership in the File Inspector (right panel).
Configure JavaScript loading
Update your AppDelegate to load the bundled JavaScript instead of Metro.
For Objective-C (AppDelegate.m)
Find the jsCodeLocation configuration in the application:didFinishLaunchingWithOptions: method, and replace it with:
jsCodeLocation = [[NSBundle mainBundle]
URLForResource:@"main"
withExtension:@"jsbundle"];If you see conditional DEBUG code like this, the Release build already uses the bundle:
#if DEBUG
jsCodeLocation = [[RCTBundleURLProvider sharedSettings]
jsBundleURLForBundleRoot:@"index"];
#else
jsCodeLocation = [[NSBundle mainBundle]
URLForResource:@"main"
withExtension:@"jsbundle"];
#endifFor Swift (AppDelegate.swift)
Replace the jsCodeLocation setup with:
let jsCodeLocation = Bundle.main.url(forResource: "main",
withExtension: "jsbundle")If conditional DEBUG code is present, the Release build already uses the bundle:
#if DEBUG
let jsCodeLocation = RCTBundleURLProvider.sharedSettings()
.jsBundleURL(forBundleRoot: "index")
#else
let jsCodeLocation = Bundle.main.url(forResource: "main",
withExtension: "jsbundle")
#endifBuild the app
- Select your target simulator in Xcode, ideally with a recent iOS version.
- Choose your build scheme:
- Debug: Larger file size, includes diagnostics
- Release: Optimized build, recommended for testing
- Navigate to Product → Clean Build Folder (⇧⌘K)
- Navigate to Product → Build (⌘B)
- Wait for the build to finish
Test your build offline
This check confirms the JavaScript is inside the build:
- Disable network connectivity by turning off Wi-Fi
- Close all terminal windows so Metro is not running
- Open iOS Simulator
- Navigate to Product → Show Build Folder in Finder
- Go to
Build/Products/Debug-iphonesimulator/for Debug orBuild/Products/Release-iphonesimulator/for Release - Drag the
.appfile into the simulator to install it - Launch the app and verify it starts without errors
- Test core functionality to confirm everything works
- Re-enable the network
Package and upload
Zip the
.appwithditto, so the.appsits at the top level of the zip:ditto -c -k --sequesterRsrc --keepParent YourApp.app YourApp.zipSelecting Compress on the
.appin Finder makes the same zip. Don’t upload an.ipa: Pie refuses it.In Pie, open Builds and select Upload build
Choose the zipped
.app, enter a Build ID, and select Submit for testing
Restore development settings
Optional: if you changed AppDelegate.m or AppDelegate.swift, put the original code back to keep developing locally with Metro.
Automated bundling method
If your project has the “Bundle React Native code and images” build phase:
Open your project:
open ios/YourProjectName.xcworkspaceSelect your app target → Build Settings
Search for Build Configuration and set it to Release
Navigate to Product → Clean Build Folder (⇧⌘K)
Navigate to Product → Build (⌘B)
The build creates the JavaScript bundle for you
Locate your
.appfile in Product → Show Build Folder in FinderTest offline, zip the
.appwithditto, and upload it
You don’t need to bundle by hand or change any code. The Release configuration takes care of it.
What you’ll see
Build uploaded means the file reached Pie, and the build appears in Builds. It doesn’t mean Pie accepted the build. If Pie refuses it, such as a device build, the portal shows nothing more, and the build can still be picked under Select Build. Pick your simulator build there when you start a run. Then tell Pie how to sign in and what to test with the post-upload requirements.
Hermes engine
If your project uses Hermes, the default in React Native 0.70 and later:
- Bundling works the same way.
- Hermes produces smaller, faster bundles.
- Check
ios/Podfileforhermes_enabled = true. - Make sure all dependencies work with Hermes.
Build scheme comparison
| Feature | Release | Debug |
|---|---|---|
| Bundle size | Smaller | Larger |
| Performance | Optimized | Unoptimized |
| Debug symbols | No | Yes |
| Recommended for | Pie testing | Issue diagnosis |
Use Release builds for Pie, because they behave like production. For more, see the React Native publishing guide.
Troubleshooting
Blank or white screen
Possible causes:
- Bundle not found in the app bundle
- Incorrect entry file specified
- Hermes compatibility issues
Solutions:
- Verify
main.jsbundlehas Target Membership checked in Xcode - Confirm the entry file name, and check
package.jsonfor a custom entry - If you use Hermes, make sure all dependencies work with it
App tries to connect to Metro (localhost:8081)
Possible causes:
- AppDelegate still in development mode
- Debug scheme selected instead of Release
- Cached build artifacts
Solutions:
- Verify AppDelegate changes were saved
- Switch to the Release scheme or update the AppDelegate code
- Clean the build folder and rebuild: Product → Clean Build Folder
- Search your codebase for hardcoded
localhost:8081references
Assets not loading (missing images or fonts)
Possible causes:
- Assets folder not added to Xcode
- Assets not included in the target
- Incorrect asset paths in code
Solutions:
- Verify the assets folder was added with Copy items if needed
- Check Target Membership in the File Inspector
- Confirm your code uses correct relative paths for assets
Native modules failing
Possible causes:
- CocoaPods not installed
- Static linking issues
- New architecture compatibility (React Native 0.68 and later)
Solutions:
- Run
cd ios && pod install && cd .. - Check module documentation for static build requirements
- Check that each module works with the React Native architecture
Build errors in Xcode
Possible causes:
- Hermes configuration issues
- Missing bundle file
- Duplicate symbols
Solutions:
- Verify
hermes_enabledinios/Podfile - Confirm
ios/main.jsbundleexists - Delete DerivedData:
rm -rf ~/Library/Developer/Xcode/DerivedData
Still stuck? Contact support@pie.inc with your React Native version, build errors or console logs, screenshots of relevant issues, and what you have tried.