はじめに
本番運用中のReact Nativeアプリ(Android / iOS)で、OTA(Over-The-Air)アップデートにCodePushを利用していました。
しかし、Visual Studio App Centerの提供終了に伴い、CodePushのクラウドサービスも利用できなくなりました。
OTAがなくなると、JSの小さな不具合修正でもストア審査を通す必要があり、ユーザーに修正が届くまで数日かかってしまいます。
この記事では、React Native CLI(bare workflow)のプロジェクトをCodePushからEAS Updateへ移行した手順と、実際にハマったポイントをまとめます。
対象読者
- React Native CLI(bare)でCodePushを使っている方
- OTAアップデートの移行先を検討している方
- EAS Updateのセキュリティ(署名付きアップデート)に興味がある方
環境
| 項目 | バージョン |
|---|---|
| React Native | 0.76.9 |
| Expo SDK | 52.0.47 |
| expo-updates | 0.27.4 |
| eas-cli | ≥ 10.0.0 (eas.json) / 23.0.0 |
| Node.js | 24 |
背景:App Center / CodePushの終了
App Centerは2025年3月31日に提供終了となり、CodePushのクラウドサービスも使えなくなりました。
移行先を決めないまま放置すると、次の問題が発生します。
- 緊急のバグ修正をすぐに配信できない
- すべての修正がストア審査待ちになる
- リリースサイクルが遅くなる
移行先の比較
| 選択肢 | メリット | デメリット |
|---|---|---|
| code-push-server(セルフホスト) | 既存のSDKをほぼそのまま使える | サーバーの運用・保守コストが発生する |
| EAS Update | Expoエコシステムと統合、運用が楽 | 利用量に応じた課金 |
今回はインフラ運用の負担を減らし、署名付きアップデートでセキュリティも確保できる点を重視して、EAS Updateを選びました。
移行手順
1. Expoモジュールの導入
bare workflowのプロジェクトでexpo-updatesを使うには、まずExpoモジュールの基盤を導入します。
npx install-expo-modules@latest
2. expo-updatesのインストール
npx expo install expo-updates
3. EAS Updateの設定
npm install -g eas-cli
eas login
eas update:configure
実行後、以下が設定されているか必ず確認してください。
-
app.jsonのupdates.url - Android:
AndroidManifest.xmlのexpo.modules.updates.EXPO_UPDATE_URL - iOS:
Expo.plistのEXUpdatesURL
4. runtimeVersionの設計
ここが移行で一番重要なポイントです。
runtimeVersion は「このJSバンドルがどのネイティブビルドと互換性があるか」を示します。
{
"expo": {
"runtimeVersion": "1.0.0",
"updates": {
"url": "https://u.expo.dev/<project-id>",
"checkAutomatically": "ON_LOAD",
"fallbackToCacheTimeout": 0
}
}
}
ルールはシンプルです。
- JSだけの変更 → OTAで配信(runtimeVersionはそのまま)
- ネイティブの変更(ライブラリ追加、権限変更など) → runtimeVersionを上げて、ストアで新しいビルドを配信
手動管理が不安な場合は、ネイティブ部分の変更を自動検知する fingerprint ポリシーも検討してください。
5. チャネル設計
eas.json でビルドごとにチャネルを分けます。
{
"build": {
"preview": {
"distribution": "internal",
"channel": "preview"
},
"production": {
"channel": "production"
}
}
}
まず preview で社内確認し、問題なければ production に配信する運用にしました。
6. CodePush SDKの削除
EAS Updateが動作することを確認してから、CodePushを削除します。
npm uninstall react-native-code-push
JS側:codePush() のラッパーを削除します。
- import codePush from 'react-native-code-push';
-
- export default codePush(App);
+ export default App;
ネイティブ側も忘れずに削除します。
- Android:
build.gradleのcodepush.gradleの読み込み、MainApplicationのgetJSBundleFile()、strings.xmlのデプロイメントキー - iOS:
AppDelegateの[CodePush bundleURL]、Info.plistのCodePushDeploymentKey
7. アップデートの配信
eas update --channel production --message "fix: ログイン画面の不具合修正"
(任意)アプリ内でのアップデート確認
起動時以外にも確認したい場合は、手動でチェックできます。
import * as Updates from 'expo-updates';
export async function checkForAppUpdate() {
if (__DEV__) return;
try {
const update = await Updates.checkForUpdateAsync();
if (update.isAvailable) {
await Updates.fetchUpdateAsync();
await Updates.reloadAsync();
}
} catch (error) {
// アップデート失敗時もアプリは現在のバンドルで動作を継続する
console.warn('Update check failed:', error);
}
}
ハマったポイント
アップデートが端末に届かない
原因のほとんどは runtimeVersionの不一致 でした。
ストアのビルドと配信したアップデートのruntimeVersionが違うと、アップデートは無視されます。
EASのダッシュボードで、ビルドとアップデートのruntimeVersionが一致しているか確認しましょう。
ネイティブ設定の書き漏れ
bare workflowでは、app.json の設定だけでは不十分な場合があります。
AndroidManifest.xml と Expo.plist に値が反映されているか、必ず確認してください。
開発ビルドでの動作確認
__DEV__ 環境ではOTAが動作しません。
確認は必ず preview チャネルのリリースビルドで行いましょう。
セキュリティ:署名付きアップデート
OTAアップデートは「アプリのコードを外部から差し替える仕組み」です。
つまり、配信経路が攻撃されると 悪意のあるJSバンドルが注入される リスクがあります。
expo-updatesにはコード署名の機能があり、自分の秘密鍵で署名されたバンドルだけをアプリが受け付けるように設定できます。
鍵と証明書の生成
npx expo-updates codesigning:generate \
--key-output-directory keys \
--certificate-output-directory certs \
--certificate-validity-duration-years 10 \
--certificate-common-name "My App"
アプリ側の設定
{
"expo": {
"updates": {
"codeSigningCertificate": "./certs/certificate.pem",
"codeSigningMetadata": {
"keyid": "main",
"alg": "rsa-v1_5-sha256"
}
}
}
}
署名付きで配信
eas update --channel production --private-key-path keys/private-key.pem
秘密鍵(keys/)は絶対にGitにコミットしないでください。.gitignore に追加し、CI環境ではシークレットとして管理しましょう。
コード署名はEASのプランによって利用可否が異なる場合があります。導入前に公式ドキュメントを確認してください。
ロールバック戦略
問題のあるアップデートを配信してしまった場合は、以前の正常なアップデートを再配信します。
eas update:list --branch production
eas update:republish --group <正常だったupdate-group-id>
事前に preview チャネルで確認するフローを徹底することで、ロールバックが必要になる場面自体を減らせます。
まとめ
- CodePushからEAS Updateへの移行は、bare workflowでも段階的に進められる
- 一番重要なのは runtimeVersionの設計。JSの変更とネイティブの変更を明確に分ける
- OTAは便利な反面、攻撃経路にもなりうる。署名付きアップデートでバンドルの改ざんを防ぐ
同じように移行を検討している方の参考になれば嬉しいです。