前言
最近在用 Expo 裸工作流开发一个工具类 App,模拟器跑起来丝滑顺畅,可一到真机打包就接连翻车。从 No script URL provided 红色全屏报错,到深度链接配置缺失,再到 main.jsbundle 反复消失,前后折腾了好几个小时才彻底搞定。这里把完整的踩坑过程、每一步操作细节和关键避坑点都记录下来,给同样遇到问题的朋友做个参考,少走弯路。
一、环境背景
项目类型:Expo 裸工作流(Bare Workflow)React Native 项目
技术栈:React Native + Expo Router + TypeScript
开发环境:MacOS + Xcode 26 + pnpm 包管理器
目标:打包可在 iOS 真机离线运行的 Ad Hoc 签名 IPA,无需依赖 Metro 开发服务器,无调试横幅,App 能正常启动并运行所有功能。
二、问题 1:真机启动直接报错 No script URL provided
现象
模拟器上运行完全正常,能正常连接 Metro 开发服务器加载 JS 代码。
用 Xcode 打了 Release 模式的包,安装到真机后,直接弹出红色全屏报错:
plaintext
No script URL provided. Make sure the packager is running or you have embedded a JS bundle in your application bundle.
unsanitizedScriptURLString = (null)

App 无法进入主界面,只能看到报错页面,底部有 Reload JS 等调试按钮。
初步排查:main.jsbundle 到底在哪?
第一反应:是不是 main.jsbundle 没生成?
我直接用了纯 React Native 项目的打包命令:
bash
运行
pnpm expo export:embed \
--platform ios \
--dev false \
--bundle-output ./ios/main.jsbundle \
--assets-dest ./ios
终端显示打包成功,ios/ 目录下也生成了 main.jsbundle 文件,可真机还是报错。
关键验证:文件有没有真正被打进 IPA 里?
我把导出的 .ipa 后缀改成 .zip 解压,打开 Payload/app.app/ 目录,发现里面根本没有 main.jsbundle 文件。原来,光生成文件没用,Xcode 工程没有把它标记为要打包进 App 的资源,打包时自然不会包含进去。
踩坑:添加文件时选错了选项
我右键 ios/app/ 文件夹,选择 Add Files to "app"...,把 main.jsbundle 加了进去,但还是报错。后来才发现,添加文件时默认选了 Reference files in place,这个选项只是让 Xcode 引用文件路径,不会把文件复制到工程目录里,打包时不会被嵌入。
Expo 命令兼容问题
我尝试用 Expo 官方的 expo export:embed 命令生成文件,结果又报错:
plaintext
CommandError: Missing required argument: --bundle-output
原来不同版本的 Expo,export:embed 命令的参数有变化,必须手动指定输出路径。
三、问题 2:解决了 JS 包问题,又遇到深度链接报错
现象
把 main.jsbundle 正确添加到工程后,真机不再报 No script URL provided 了,但弹出了新的 JS 报错:
plaintext
Error: Cannot make a deep link into a standalone app with no custom scheme defined
报错栈信息明确指向 main.jsbundle,说明 JS 包已经被成功加载了,但代码运行时触发了错误。
排查过程
这个报错是因为我的项目用了 expo-router,它依赖自定义 URL Scheme 来处理路由跳转,但我在 app.json 里完全没配置 scheme 字段,导致 expo-router 找不到跳转协议,直接抛出错误。
四、问题 3:重置 iOS 工程后,main.jsbundle 又消失了
现象
为了修复深度链接问题,我执行了 pnpm expo prebuild --clean --platform ios 重置 iOS 工程,结果打开 ios/ 目录,之前生成的 main.jsbundle 不见了,Xcode 里手动添加的文件也消失了,所有配置都被重置了。
原因
expo prebuild --clean 命令会完全重置 iOS 工程,把 Expo 自动生成的配置恢复到初始状态,所有手动添加的文件和修改都会被清空,这也是很多人踩的坑。
五、问题 4:Debug 模式下顶部一直显示调试横幅
现象
在 Xcode Debug 模式下真机运行,App 顶部一直显示 Connect to Metro to develop JavaScript.,还有 Reload JS 按钮,看着很别扭,担心打包出来也会有这个横幅。
原因
这个横幅是 Expo Dev Client 自带的调试提示,只有 Debug 模式的包才会显示,Release 模式打包的 IPA 会自动移除所有调试相关的 UI,完全不用额外配置。
六、完整解决步骤(按顺序执行,一步都别漏)
步骤 1:修正 app.json,配置 URL Scheme
首先解决深度链接报错,给 expo-router 配置自定义跳转协议,修改项目根目录的 app.json:
json
{
"expo": {
"name": "大纲匹配工具",
"slug": "outline-match-tool",
"version": "1.0.25",
"orientation": "portrait",
"icon": "./assets/icon.png",
"userInterfaceStyle": "light",
"scheme": "outlinematch", // 新增这一行,自定义小写无空格协议
"ios": {
"supportsTablet": true,
"buildNumber": "1",
"bundleIdentifier": "com.zgamecsoft.gameantz"
},
"android": {
"adaptiveIcon": {
"foregroundImage": "./assets/adaptive-icon.png",
"backgroundColor": "#ffffff"
},
"predictiveBackGestureEnabled": false,
"versionCode": 1,
"package": "com.zgamecsoft.gameantz"
},
"web": {
"bundler": "metro",
"favicon": "./assets/favicon.png"
},
"plugins": [
"expo-router",
"expo-asset",
"expo-sqlite",
"expo-font",
"expo-system-ui",
[
"expo-image-picker",
{
"photosPermission": "允许 大纲匹配工具 访问相册以选择图片"
}
],
[
"expo-media-library",
{
"photosPermission": "允许 大纲匹配工具 保存图片到相册",
"savePhotosPermission": "允许 大纲匹配工具 保存图片到相册"
}
],
"expo-file-system",
"expo-image"
],
"router": {
"root": "src"
},
"experiments": {
"typedRoutes": true
}}
}
步骤 2:重置并生成新的 iOS 工程
执行 expo prebuild --clean 重置 iOS 工程,让 app.json 的配置生效:
bash
运行
清理旧的 iOS 工程
rm -rf ios/
重新生成 iOS 工程
pnpm expo prebuild --clean --platform ios
安装 iOS 依赖
cd ios && pod install && cd ..
步骤 3:用 Expo 兼容命令生成 main.jsbundle
用 Expo 官方的 export:embed 命令生成 JS 包,指定输出路径和参数:
bash
运行
pnpm expo export:embed \
--platform ios \
--dev false \
--bundle-output ./ios/main.jsbundle \
--assets-dest ./ios
--platform ios:指定生成 iOS 平台的 JS 包
--dev false:生成 Release 模式的压缩 JS 包,移除调试代码
--bundle-output ./ios/main.jsbundle:指定 JS 包的输出路径
--assets-dest ./ios:指定静态资源的输出路径
执行完成后,你会在 ios/ 目录下看到新生成的 main.jsbundle 文件,大小在 4-5MB 左右,说明生成成功。
步骤 4:把 main.jsbundle 添加到 Xcode 工程(关键步骤)
打开 ios/app.xcworkspace(注意:必须打开 .xcworkspace,不能打开 .xcodeproj)
在左侧文件列表,右键 app 文件夹 → 选择 Add Files to "app"...
找到并选择 ios/main.jsbundle 文件,在弹出的窗口中:
Action 必须选择 Copy files to destination(不要选 Reference files in place)
Targets 必须勾选你的 app Target
点击 Add,文件就会被添加到工程里,同时复制到工程目录中。
步骤 5:修改 AppDelegate.swift,强制 Release 模式加载本地 JS 包
打开 ios/app/AppDelegate.swift,找到 ReactNativeDelegate 类,修改 bundleURL() 方法,确保 Release 模式下强制读取本地 main.jsbundle:
swift
class ReactNativeDelegate: ExpoReactNativeFactoryDelegate {
// Extension point for config-plugins
override func sourceURL(for bridge: RCTBridge) -> URL? {
// needed to return the correct URL for expo-dev-client.
bridge.bundleURL ?? bundleURL()}
override func bundleURL() -> URL? {
if DEBUG
return RCTBundleURLProvider.sharedSettings().jsBundleURL(forBundleRoot: ".expo/.virtual-metro-entry")else
// 强制读取本地 main.jsbundle
return Bundle.main.url(forResource: "main", withExtension: "jsbundle")endif
}
}
这个修改会让 App 在 Release 模式下,直接去 app.app 包里找 main.jsbundle,找不到就直接崩溃报错,方便定位问题。
步骤 6:配置 Xcode 打包脚本,禁用 Metro 服务器
打开 Target → Build Phases → 找到 Bundle React Native code and images 脚本,在脚本的最开头添加两行:
bash
运行
export NODE_BINARY=node
export RCT_NO_LAUNCH_PACKAGER=1
RCT_NO_LAUNCH_PACKAGER=1 会强制 App 启动时不找 Metro 开发服务器,直接加载本地 JS 包,避免出现 No script URL provided 报错。
步骤 7:清理并重新打包 Release 模式 IPA
在 Xcode 顶部菜单,点击 Product → Clean Build Folder(快捷键 Cmd+Shift+K),清理构建缓存。
点击 Product → Scheme → Edit Scheme,在弹出的窗口中:
左侧选中 Archive,把 Build Configuration 改成 Release
左侧选中 Run,把 Build Configuration 也改成 Release
顶部设备栏选择 Any iOS Device (arm64),不要选模拟器。
执行 Product → Archive,等待编译完成。
归档完成后,在弹出的 Organizer 窗口中,选中刚打的包,点击 Distribute App,选择 Ad Hoc 模式导出 IPA。
七、关键避坑总结
main.jsbundle 必须真正嵌入工程:只生成文件没用,必须在 Xcode 里添加并勾选 Copy items if needed,否则不会被打包进 IPA。
expo prebuild --clean 会重置工程:每次执行重置后,都要重新生成并添加 main.jsbundle,不然会被清空。
AppDelegate.swift 是兜底关键:手动修改 bundleURL() 方法,强制 Release 模式读取本地文件,比依赖自动脚本更稳。
expo-router 必须配置 scheme:不配置会导致深度链接报错,App 无法正常启动。
Debug 模式的调试横幅是正常的:只有 Release 模式打包的 IPA 才会自动移除,不用额外配置隐藏。
必须打开 .xcworkspace 工程:打开 .xcodeproj 会找不到 Pods 配置,导致打包失败。
八、最终效果
按以上步骤操作后,打包出来的 Release IPA 安装到真机:
✅ 不再出现 No script URL provided 红色报错
✅ 顶部调试横幅彻底消失,界面干净清爽
✅ 完全离线运行,不需要依赖 Metro 开发服务器
✅ expo-router 路由正常工作,无深度链接报错
✅ 所有功能正常运行,无崩溃闪退
折腾下来才发现,问题的核心不是 main.jsbundle 没生成,而是 Expo 裸工作流的打包逻辑和纯 RN 项目不一样,很多默认配置都需要手动兜底。希望这篇超详细的记录能帮到遇到同样问题的朋友,少走几天弯路。
Expo_React Native 真机离线打包踩坑全记录(超详细版).docx
欢迎交流:企鹅 27800640
本文由 haolinks 创作,采用 知识共享署名4.0 国际许可协议进行许可。
本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名。