React Nativeアプリのリリースサイクルを劇的に短縮できるOTA(Over-the-Air)アップデート。ストア審査を経ずにJavaScriptバンドルを差し替えられるため、バグ修正やUI調整を数分でユーザーに届けられます。
一方で、「何が更新できて何ができないのか」「App Storeのポリシーに抵触しないか」「どのツールを選べばよいか」といった疑問を抱えるチームは少なくありません。2025年3月にはMicrosoft CodePush(App Center)がサービス終了し、OTAツールの選択肢が大きく変わりました。
ここでは2026年2月時点の最新情報をもとに、OTAの仕組みからEAS Updateの導入手順、代替ツールとの比較、本番環境での運用設計までを体系的に整理します。
OTAアップデートの基本的な仕組み
OTA(Over-the-Air)アップデートは、アプリストアを介さずにネットワーク経由でアプリの一部を更新する技術です。React Nativeアプリは大きく2つのレイヤーで構成されています。
| レイヤー | 内容 | OTAでの更新 |
|---|---|---|
| ネイティブレイヤー | Swift/Kotlin、ネイティブモジュール、権限設定、SDKバージョン | 不可(ストア再提出が必要) |
| JavaScriptレイヤー | JSバンドル、スタイル、画像アセット、翻訳ファイル | 可能 |
OTAで更新可能な範囲は「JavaScriptインタプリタ上で実行されるコード」に限られます。具体的には以下が対象です。
OTAで更新できるもの:
- JavaScriptコード(ビジネスロジック、画面遷移、状態管理)
- UIスタイリング(StyleSheet、レイアウト変更)
- テキスト・翻訳リソース
- 画像アセット(JSバンドルに含まれるもの)
OTAでは更新できないもの:
- ネイティブモジュールの追加・変更
- アプリアイコン・スプラッシュスクリーン
app.jsonのschemeやownerの変更- Expo SDKバージョンのアップグレード
- アプリ権限(カメラ、位置情報など)の追加
ios/・android/ディレクトリ配下のネイティブコード
更新の適用タイミングも重要です。OTAアップデートは一般的にアプリ起動時にバックグラウンドでダウンロードされ、次回起動時に反映されます。ストアから初回インストールした直後の起動ではOTAアップデートは反映されません。
App StoreとGoogle Playのポリシー
OTAアップデートを導入する際に最も注意が必要なのがストアポリシーへの準拠です。
Apple(App Store)
Apple Developer Program License Agreementのセクション3.3.1(B)では、「インタプリタで実行されるコード」のダウンロードを条件付きで認めています。
許可される条件は次の3つです。
- アプリの主目的を変更しないこと(ストア申請時の説明と矛盾する機能追加はNG)
- コードやアプリのストアフロントを構築しないこと
- OSの署名・サンドボックス・セキュリティ機能をバイパスしないこと
React NativeのJavaScriptバンドル更新はこの「interpreted code」に該当し、上記3条件を守る限りOTAは利用可能です。バグ修正やパフォーマンス改善は一般的に問題ありません。
Google Play
Google PlayのDevice and Network Abuseポリシーでは、Google Play以外からの実行可能コードのダウンロードを原則禁止していますが、明確な例外規定があります。
「仮想マシンまたはインタプリタで実行されるコード(WebViewやブラウザ内のJavaScriptなど)には、この制限は適用されない」
つまりReact NativeのJavaScriptバンドル配信はポリシー上の例外に該当します。ただし、ランタイムで読み込まれるコードがGoogle Playポリシーに違反する内容を含んではなりません。
実務上の判断基準
| 判断ポイント | OTA配信OK | ストア再提出推奨 |
|---|---|---|
| バグ修正・パフォーマンス改善 | ○ | - |
| テキスト修正・翻訳追加 | ○ | - |
| UIレイアウト調整 | ○ | - |
| 新規画面・機能の追加 | △(軽微なら可) | 大幅な機能追加 |
| 課金機能の変更 | - | ○(必須) |
| ネイティブモジュール変更 | - | ○(必須) |
EAS Updateの導入と使い方
Expo Application Servicesが提供するEAS Updateは、2026年時点でReact Native + ExpoプロジェクトにおけるOTA配信の標準的な選択肢です。
セットアップ手順
1. expo-updatesのインストール
npx expo install expo-updates
2. EAS Updateの設定
eas update:configure
このコマンドでeas.jsonにアップデート関連の設定が追加され、app.jsonにupdatesセクションが構成されます。
3. app.jsonの設定例
{
"expo": {
"updates": {
"url": "https://u.expo.dev/your-project-id",
"enabled": true,
"checkAutomatically": "ON_LOAD",
"fallbackToCacheTimeout": 0
},
"runtimeVersion": {
"policy": "fingerprint"
}
}
}
checkAutomaticallyには4つの選択肢があります。
| 値 | 動作 |
|---|---|
ON_LOAD | アプリ起動時に自動チェック(デフォルト) |
WIFI_ONLY | Wi-Fi接続時のみ自動チェック |
ON_ERROR_RECOVERY | エラー復旧時のみチェック |
NEVER | 自動チェックを無効化(手動制御用) |
4. アップデートの配信
eas update --channel production --message "Fix: ホーム画面のレイアウト崩れを修正"
--channelでデプロイ先の環境を指定します。ブランチとチャネルのマッピングにより、開発・ステージング・本番を分離できます。
手動アップデートの実装
checkAutomatically: "NEVER"に設定し、更新チェックとUIをアプリ側で制御するパターンです。ユーザーにアップデートの存在を通知し、任意のタイミングで適用できます。
import * as Updates from 'expo-updates';
import { Alert } from 'react-native';
async function checkAndApplyUpdate(): Promise<void> {
try {
const update = await Updates.checkForUpdateAsync();
if (!update.isAvailable) return;
const result = await Updates.fetchUpdateAsync();
if (!result.isNew) return;
Alert.alert(
'アップデート完了',
'新しいバージョンが利用可能です。再起動しますか?',
[
{ text: 'あとで', style: 'cancel' },
{ text: '再起動', onPress: () => Updates.reloadAsync() },
]
);
} catch (error) {
console.error('OTA update check failed:', error);
}
}
useUpdates()フックを使えば、ダウンロード進捗(downloadProgress)の表示やステータス管理をよりReactらしく実装できます。
フォアグラウンド復帰時の自動チェック
アプリがバックグラウンドからフォアグラウンドに戻ったタイミングでアップデートをチェックする実装パターンも有用です。
import { useEffect } from 'react';
import { AppState, AppStateStatus } from 'react-native';
import * as Updates from 'expo-updates';
function useOTAUpdateOnForeground(): void {
useEffect(() => {
const handleAppStateChange = (nextState: AppStateStatus) => {
if (nextState === 'active') {
checkAndApplyUpdate();
}
};
const subscription = AppState.addEventListener('change', handleAppStateChange);
return () => subscription.remove();
}, []);
}
runtimeVersionとfingerprintによる互換性管理
OTAアップデートで最も重要な設計要素がruntimeVersionです。ビルドとアップデートのruntimeVersionが一致しない場合、アップデートは適用されません。この仕組みにより、ネイティブコードが変わったビルドに互換性のないJSバンドルが配信される事故を防ぎます。
3つのポリシー
app.jsonのruntimeVersionにはカスタム文字列またはポリシーを指定できます。
| ポリシー | 生成されるruntimeVersion | 用途 |
|---|---|---|
appVersion | "1.2.0" | バージョン番号と1:1対応させたい場合 |
nativeVersion | "1.2.0(3)" | ビルド番号も含めて管理したい場合 |
fingerprint | "a1b2c3d4..." (ハッシュ) | ネイティブ依存関係の変更を自動検知したい場合 |
fingerprintポリシーの仕組み
fingerprintポリシーは@expo/fingerprintパッケージが提供する機能で、プロジェクトのネイティブレイヤー全体からハッシュ値を自動計算します。
ハッシュの計算対象には以下が含まれます。
node_modules内のネイティブモジュールios/・android/配下のカスタムネイティブコード- Expo設定ファイル
package.jsonの依存関係
ネイティブに影響する変更(例: 新しいネイティブモジュールの追加)があるとハッシュ値が変わり、新しいビルドが必要であることが自動で判定されます。手動でruntimeVersionを管理する必要がなくなるため、中〜大規模プロジェクトではfingerprintポリシーの採用が推奨されます。
プラットフォーム別設定
iOSとAndroidで異なるruntimeVersionを設定することも可能です。プラットフォーム固有のネイティブモジュールを使っている場合に有用です。
{
"expo": {
"ios": {
"runtimeVersion": { "policy": "fingerprint" }
},
"android": {
"runtimeVersion": "1.0.0"
}
}
}
fingerprintのカスタマイズ
.fingerprintignoreファイルで特定のファイルをハッシュ計算から除外できます。CI環境固有のファイルや、ネイティブに影響しない設定ファイルを除外する際に使います。
# .fingerprintignore
android/app/google-services.json
ios/GoogleService-Info.plist
またfingerprint.config.jsでより高度なカスタマイズも可能です。
EAS Updateの料金体系
EAS Updateは無料プランから利用可能ですが、MAU(月間アクティブアップデートユーザー)や帯域幅に応じて有料プランが必要になります。
| 項目 | Free | Starter($19/月) | Production($199/月) | Enterprise($1,999〜/月) |
|---|---|---|---|---|
| MAU上限 | 1,000 | 3,000 | 50,000 | 1,000,000 |
| グローバルエッジ帯域幅 | 100 GiB | 500 GiB | 1 TiB | 40 TiB |
| ストレージ | 20 GiB | 20 GiB | 1 TiB | 10 TiB |
| 超過MAU | - | 従量課金 | 従量課金 | 従量課金 |
| 超過帯域幅 | - | $0.10/GiB | $0.10/GiB | $0.10/GiB |
※ MAUは「請求期間中に少なくとも1回アップデートをダウンロードしたユニークインストール」として計算されます。
個人開発やプロトタイプであればFreeプランで十分です。ユーザー数が数千人規模のプロダクションアプリではStarterまたはProductionプランを検討してください。
OTAツールの比較:EAS Update・Hot Updater・その他
2025年3月のCodePush終了以降、React NativeのOTAツールは選択肢が多様化しています。主要なツールを比較します。
| 特性 | EAS Update | Hot Updater | Expo Open OTA | セルフホストCodePush |
|---|---|---|---|---|
| 提供形態 | クラウドサービス | セルフホスト(OSS) | セルフホスト(OSS) | セルフホスト(OSS) |
| 対応フレームワーク | Expo(React Native) | React Native(Expo含む) | Expo(React Native) | React Native |
| 管理UI | Expoダッシュボード | Webコンソール付属 | なし | なし |
| ストレージ | Expo CDN | S3/R2/Supabase/Firebase | AWS S3 | Azure Blob等 |
| 料金 | 無料〜$1,999+/月 | インフラ費のみ | インフラ費のみ | インフラ費のみ |
| New Architecture対応 | ○ | ○ | ○ | △ |
| セットアップ難易度 | 低い | 中程度 | 中程度 | 高い |
| ロールバック | Republish機能 | Webコンソールから | 手動 | 手動 |
| GitHub Stars | - | 約1,300 | 約220 | 約300 |
(各GitHubリポジトリの情報をもとに2026年2月時点で作成)
選定の指針
EAS Updateが適するケース:
- Expoのマネージドワークフローを使用中
- インフラ運用の負担を減らしたい
- ダッシュボードでの可視化や段階的ロールアウトが欲しい
Hot Updaterが適するケース:
- OTAのインフラを自社で完全に管理したい
- Cloudflare R2等を使ってコストを最小限に抑えたい
- Bare Workflowを含むReact Nativeプロジェクト
Expo Open OTAが適するケース:
- Expoのプロトコルは使いたいがクラウド費用を抑えたい
- データベース不要のシンプルな構成を好む
本番環境でのOTA運用設計
OTAアップデートは便利ですが、本番環境では慎重な運用ルールが不可欠です。
配信チャネルの分離
開発・ステージング・本番の3環境を分離し、各環境に専用のチャネルを割り当てます。
# 開発環境へのOTA配信
eas update --channel development --message "開発中の機能テスト"
# ステージング環境への配信
eas update --channel staging --message "QA検証用ビルド"
# 本番環境への配信(慎重に)
eas update --channel production --message "v1.2.1 バグ修正"
本番OTAの運用ルール例
大規模サービスで実績のある運用パターンとして、以下のようなルール設計が考えられます。
原則: 本番環境ではOTAよりストア提出を優先する
OTAを本番で使用するのは以下の場合に限定します。
- 緊急のバグ修正(クラッシュやデータ不整合の即時対応)
- 軽微なテキスト修正(誤字脱字の修正など)
- ストア審査待ちの間に発覚した不具合の暫定対処
新機能の追加や大幅なUI変更は、たとえOTA可能な範囲であってもストア提出で行います。理由は以下のとおりです。
- ストアのバージョン管理と一貫性を保てる
- ユーザーがアプリストアのアップデート履歴から変更内容を確認できる
- ストアポリシー違反のリスクを最小化できる
CI/CDパイプラインとの統合
GitHub Actionsを使ったOTA自動配信ワークフローの例です。
name: OTA Update (Development)
on:
push:
branches: [develop]
jobs:
update:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- uses: expo/expo-github-action@v8
with:
eas-version: latest
token: ${{ secrets.EXPO_TOKEN }}
- run: eas update --channel development --auto
本番環境向けには手動トリガー(workflow_dispatch)を推奨します。
name: OTA Update (Production)
on:
workflow_dispatch:
inputs:
message:
description: 'アップデートメッセージ'
required: true
jobs:
update:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- uses: expo/expo-github-action@v8
with:
eas-version: latest
token: ${{ secrets.EXPO_TOKEN }}
- run: eas update --channel production --message "${{ inputs.message }}"
ロールバック戦略
EAS Updateでは、Republish機能で以前の安定版アップデートを再公開することでロールバックが可能です。
# 特定のアップデートを再公開
eas update:republish --group <update-group-id>
障害発生時の手順は次のとおりです。
- 障害の検知(エラー監視ツール: Sentry、Datadogなど)
- 問題のあるアップデートの特定
- 直前の安定版をRepublishで再公開
- 修正版の開発・テスト
- 修正版のOTA配信
OTA導入時のよくあるトラブルと対処法
アップデートが反映されない
最も多いトラブルです。以下を確認してください。
- runtimeVersionの不一致: ビルドとアップデートのruntimeVersionが一致しているか確認。
fingerprintポリシー使用時は、ネイティブ依存関係の変更後にビルドし直す必要があります - チャネルの不一致:
eas update --channelで指定したチャネルがビルドの設定と一致しているか確認 - 反映タイミング: OTAアップデートは一般的に「ダウンロード後の次回起動」で適用されます。アプリを2回再起動してください
- 初回インストール: ストアからインストールした直後の初回起動ではOTAアップデートは反映されません
ネットワーク制限環境でOTAが動作しない
企業向け(BtoB)アプリでは、顧客企業のネットワークでファイアウォールやホワイトリストの制限によりOTAが動作しないケースがあります。
Expo EAS Updateを利用する場合、以下のドメインへの通信を許可する必要があります。
u.expo.dev(アップデートサーバー)assets.eascdn.net(アセット配信CDN)exp.host(Expoサービス)
顧客企業にネットワーク設定の変更を依頼する際は、カスタマーサクセスチームと連携して対応を進める体制が重要です。
expo build(Classic Updates)からの移行
2021年12月以前に導入されたexpo publish(Classic Updates)からEAS Updateへの移行が必要なプロジェクトは、以下の公式ガイドに従ってください。
- runtimeVersionの設定追加
- channelベースの配信設定への変更
- EAS CLI(
eas updateコマンド)への切り替え
まとめ
React NativeのOTAアップデートは、JavaScriptバンドルの差し替えによりストア審査なしで迅速にアプリを更新できる強力な仕組みです。
ツール選定では、ExpoプロジェクトにはEAS Updateが最も導入しやすく、ダッシュボードや段階的ロールアウトなどの運用機能も充実しています。コストを重視する場合やBare WorkflowではHot Updaterやセルフホスト型のソリューションも選択肢に入ります。
運用面では、fingerprintポリシーによるruntimeVersionの自動管理、開発・本番チャネルの分離、本番OTAは緊急時に限定するルール設計が安定稼働の鍵です。
EAS Updateの公式ドキュメント(https://docs.expo.dev/eas-update/introduction/)も参照し、プロジェクトに最適なOTA戦略を設計してください。
