◉RORK LABEN
●GPT-6.1 SOL — Rork のモデルメニューに追加(09-29)。Low/Medium/High を選べ、Pro・Max に含まれます●SONNET 5.5 — UI づくりが得意で、Sonnet 5 より出力が速いモデルが選べます(09-28)●SDK 58 — Beta が 09/15 から。stable の案内はまだなく、Expo Go でのプロジェクト実行は 09/03 からログインが必要です●WIDGET — iOS のウィジェットが本番ビルドだけ真っ黒になる原因を追った報告が出ています●OBSERVE — EAS Observe がネイティブクラッシュも記録します(SDK 57.0.21 以降・10/07)●NEW — 起動広告が、背面から戻った回にしか出ていませんでした。「前面に戻った」合図が、…●GPT-6.1 SOL — Rork のモデルメニューに追加(09-29)。Low/Medium/High を選べ、Pro・Max に含まれます●SONNET 5.5 — UI づくりが得意で、Sonnet 5 より出力が速いモデルが選べます(09-28)●SDK 58 — Beta が 09/15 から。stable の案内はまだなく、Expo Go でのプロジェクト実行は 09/03 からログインが必要です●WIDGET — iOS のウィジェットが本番ビルドだけ真っ黒になる原因を追った報告が出ています●OBSERVE — EAS Observe がネイティブクラッシュも記録します(SDK 57.0.21 以降・10/07)●NEW — 起動広告が、背面から戻った回にしか出ていませんでした。「前面に戻った」合図が、…
記事一覧/開発ツール
⬡ 開発ツール/2026-05-01中級

Rork で EAS Update を配信したのに反映されない — 原因を切り分ける5つの典型パターン

Rork で EAS Update を publish したのにアプリ側に反映されない、というのは慣れていてもうっかり詰まるトラブルです。よくある5つの原因と、ターミナルで5分以内に切り分ける手順を実例ベースで紹介します。

Rork584EAS Update7OTA7Expo219トラブルシューティング85iOS115Android55

「ストアへの再申請なしで修正を配信できる」というのは EAS Update(OTA アップデート)の最大の利点ですが、いざ publish したのに「自分の iPhone で再起動してもアプリが古いまま」という現象には、私も何度か遭遇しています。

厄介なのは、EAS Update は 静かに失敗する という点です。eas update コマンド自体は緑色で「Published」と出るのに、ユーザーの端末にだけ届いていません。Sentry を見ても何も出ていません。こういう「成功っぽく見えるのに反映されない」パターンは、原因が複数のレイヤーに散らばっているため、思いつきで対処すると半日が溶けます。

ここではRork で生成したアプリに EAS Update を配信した経験から、私が実際に踏んだ「反映されない」パターンを5つに整理しました。最後にターミナルで5分以内に原因を切り分ける診断フローも置いてあります。

まず最初に確認すべきこと — 「ビルドプロファイル」と「アップデートチャネル」の対応関係

EAS Update の事故は、9割が 配信先と受信側のチャネルがズレている ことから来ます。eas.json のビルドプロファイル(development、preview、production)と、eas update 時に指定するチャネル名が一致していないと、ビルド済みアプリは更新を受け取れません。

# 現在公開中のチャネルを確認
eas channel:list
 
# 特定チャネルの最新アップデート履歴を確認
eas update:list --branch production

eas.json 側はこういう構造になっているはずです:

{
  "build": {
    "production": {
      "channel": "production",
      "autoIncrement": true
    },
    "preview": {
      "channel": "preview",
      "distribution": "internal"
    }
  }
}

ここで production ビルドに対して eas update --branch preview を流していると、当然ですが production アプリには永遠に届きません。「自分は preview ビルドで確認している」のか「TestFlight 経由の production ビルドで確認している」のかを最初に確定させてください。

典型パターン①: runtime version が一致していない

EAS Update が反映されない原因として、私が一番よく踏むのがこれです。

EAS Update は「ネイティブコードを変えていないアプリにだけ JavaScript の差分を流す」仕組みなので、ネイティブコードのバージョン(runtime version)が完全一致しないと、たとえチャネルが合っていても更新を受け取りません。これは安全装置として正しい挙動なのですが、Expo SDK のアップグレードや new-architecture の有効化を挟むと runtime version が変わるため、古い端末は永久に置き去りになります。

app.json で runtime version の戦略を確認します:

{
  "expo": {
    "runtimeVersion": {
      "policy": "appVersion"
    }
  }
}

appVersion ポリシーの場合、app.json の version を変えるたびに runtime version も変わります。つまり 1.2.0 のアプリを公開済みのときに、ローカルで version を 1.3.0 に変えてから eas update を打つと、ストアの 1.2.0 ユーザーには 届きません。「ストアに出ている版に当てたいなら、ローカルの version をストアの版に戻してから publish する」のが鉄則です。

ストア側の version を確実に確認するには、App Store Connect / Google Play Console を見るのが一番早いです。

典型パターン②: クライアント側の expo-updates 設定が間違っている

app.json の updates.url と runtimeVersion、そして iOS / Android のネイティブ側に埋め込まれたチャネル ID — このどれかがビルド時にズレていると、アプリは「自分宛のアップデートはどこか」を見つけられません。Rork が生成したコードベースの場合、最初から正しく入っていることが多いのですが、後から手で編集してしまった場合は要注意です。

{
  "expo": {
    "updates": {
      "url": "https://u.expo.dev/your-project-id",
      "enabled": true,
      "checkAutomatically": "ON_LOAD",
      "fallbackToCacheTimeout": 0
    },
    "runtimeVersion": { "policy": "appVersion" }
  }
}

特に enabled: false になっていたり、checkAutomatically が ON_ERROR_RECOVERY になっていたりすると、起動のたびにはチェックしません。私はある案件で、デバッグの過程で enabled: false にしたまま production ビルドしてしまい、丸2日ハマりました。

典型パターン③: ユーザー側の端末がチェックタイミングを逃している

これは仕様で、なかなか気づきにくいポイントです。

expo-updates のデフォルト動作は「アプリ起動時に新しいアップデートをチェック → 見つけたら次回起動時に適用」という 2回起動が必要なフロー です。つまり、ユーザーが今アプリを開いていて、その状態で publish しても、すぐには切り替わりません。一度アプリを完全に閉じて(バックグラウンドではなく終了)、もう一度開く必要があります。

検証で「自分でも反映されない」と感じたら、まずアプリスイッチャーから完全に終了して2回開き直してください。それでも反映されないなら、別の原因です。

「即時反映」が要件のときは、起動後にアプリ内で能動的にチェック→再起動を行うコードを書きます:

import * as Updates from "expo-updates";
import { useEffect } from "react";
import { Alert } from "react-native";
 
export function useImmediateUpdate() {
  useEffect(() => {
    if (__DEV__) return; // 開発ビルドではスキップ
 
    (async () => {
      try {
        const update = await Updates.checkForUpdateAsync();
        if (update.isAvailable) {
          await Updates.fetchUpdateAsync();
          Alert.alert(
            "アップデートを取得しました",
            "アプリを再起動して反映します。",
            [{ text: "OK", onPress: () => Updates.reloadAsync() }]
          );
        }
      } catch (e) {
        // ネットワーク切断等は無視(次回起動時に再チェックされる)
        console.warn("[updates]", e);
      }
    })();
  }, []);
}

期待する出力としては、publish 直後に対象端末でアプリを開き直すと、ダイアログが出てそのまま再起動 → 新しい JS が動く、という流れになります。__DEV__ チェックは必須です。これを忘れると、Rork や Expo Go で開発しているときに「アップデートはありません」のエラーが連発して、開発体験が壊れます。

典型パターン④: development ビルドにアップデートを流している

eas update --branch development で配信したアップデートは、eas build --profile development で焼いた development ビルドにしか届きません。これは当たり前なのですが、社内テスターに対して「TestFlight 配ってあるからアップデート届くよね」と思って development ブランチに publish してしまう事故が時々起きます。

確認手順は単純で、自分のテスト端末がどのプロファイルでビルドされたかを eas build:list で確認します:

eas build:list --platform ios --limit 5

ここに表示される profile 列と、eas update --branch <name> の name を必ず一致させます。「TestFlight に流したのは preview プロファイルだったか production プロファイルだったか」を曖昧にしないことが、このパターンの予防策です。

典型パターン⑤: ビルドキャッシュ・端末側の埋め込み更新が悪さをしている

最後に、ここまで全部試しても反映されないときの最終手段です。

expo-updates は、起動時にダウンロード済みのアップデートを起動 → バックグラウンドで新しいアップデートを取得、というローテーションをします。何らかの理由で「古いダウンロード済みアップデートが残っている」場合、それが優先されることがあります。

切り分けるには、対象端末でアプリをいったんアンインストール → ストア(または TestFlight)から入れ直してください。これで「埋め込みバンドル」のみの状態に戻ります。その状態で起動して、反映されるかを見ます。

  • 入れ直して反映される → クライアント側のキャッシュ問題
  • 入れ直しても反映されない → publish 自体が届いていない(パターン①〜④のいずれか)

5分でできる診断フローチャート

慌てている時のために、最短の切り分け手順を置いておきます。

  1. eas channel:list で publish 先のチャネルを確認
  2. eas update:list --branch <名前> で最新の publish が登録されているか確認
  3. その publish の runtimeVersion をメモする
  4. 対象端末アプリの runtime version を確認(後述のコードでアプリ内に表示するのが楽)
  5. 一致していない → ローカルの app.json の version を戻して publish し直す
  6. 一致している → アプリを完全終了 → 2回起動して再チェック
  7. それでも来ない → アンインストール → ストアから入れ直し → 再チェック

アプリ側で runtime version を表示するには、設定画面などに以下を入れておくとデバッグが速いです:

import * as Updates from "expo-updates";
import { Text, View } from "react-native";
 
export function UpdateDebugInfo() {
  return (
    <View style={{ padding: 16 }}>
      <Text>Runtime: {Updates.runtimeVersion}</Text>
      <Text>Channel: {Updates.channel}</Text>
      <Text>Update ID: {Updates.updateId ?? "(embedded)"}</Text>
      <Text>Created: {Updates.createdAt?.toISOString() ?? "-"}</Text>
    </View>
  );
}

この4行があるだけで、「今ユーザーが動かしているのは publish 何番のバンドルか」が即座に分かります。本番ビルドでは隠したい場合、ロングタップ5秒でモーダル表示するなどのデバッグメニューを仕込んでおくのが私の好みです。

関連する周辺の知識

EAS Update の挙動をもう少し体系的に理解したい場合は、まず Rork の EAS Update / OTA 配信ガイド で基本フローを押さえてから、CI/CD と組み合わせる場合は GitHub Actions と EAS Build を使った CI/CD パイプラインの構築 を読むと、publish までの自動化までつながります。ビルド自体が通らないというときは、別記事の React Native のビルドエラーを直すためのパターン集 を先に当たってください。

書籍で React Native と Expo の挙動を腰を据えて

全体を振り返ってにかえて — 次にやること

OTA トラブルの大半は、原因がコードではなく「設定の食い違い」にあります。なので、慌てて eas update を連打する前に、最初に手元で eas channel:list と eas update:list --branch <名前> を打って、「今 publish した内容が、ちゃんと自分が思っているチャネルに登録されているか」を確認するクセをつけてください。

今日すぐにできる一手として、開発中のアプリにさきほどの UpdateDebugInfo コンポーネントを組み込んでおくことをおすすめします。次に「反映されない」が起きたとき、ユーザーの端末で動いているバージョンを目視できるだけで、診断にかかる時間が桁で変わります。

シェア

お読みいただきありがとうございます

Rork Lab は広告なしで運営しており、サーバー費用などの運営コストはメンバーシップのご支援で賄っています。実装コード・ベンチマーク・本番設計パターンなど、実務でお役立ていただける記事を毎日更新しています。もし読んでよかったと感じていただけましたら、ぜひご覧ください。

  • ✦コピー&ペーストで使える実装コード付き
  • ✦毎日新しい上級ガイドを追加
  • ✦¥580/月 または ¥1,480 の永久アクセス
メンバーシップを見る →

もしこの記事がお役に立ちましたら、チップ(¥150)で応援いただけると大変励みになります。広告なしでの運営を続けるため、皆さまのご支援が大きな力になっています。

関連記事

⬡ 開発ツール2026-10-07
日本語の見出しだけ太字にならない — Android で fontWeight が効かないときの切り分けと直し方
Rork で作ったアプリで、iPhone では太字なのに Android では日本語の見出しが細いままになる症状の切り分け記事です。原因の見分け方、fontFamily を重みごとに分ける直し方、確認用コードを載せています。
⬡ 開発ツール2026-09-21
exit code 0 で止まるビルド — ログを黙らせていた設定と、空ファイルを通した存在チェック
eas build --local が exit code 0 but produced no further output で止まる件と、0 バイトのスタブが埋め込まれず dyld で落ちる件。どちらも原因は出力を減らす設定でした。自分のスクリプトを棚卸しする手順まで書き残します。
⬡ 開発ツール2026-10-15
EXPO_PUBLIC_ の値が、開発では読めて本番だけ undefined になる書き方を、変換結果で見分けます
API の URL が開発中は読めるのに、ビルドした途端に undefined になる。原因は process.env の書き方でした。babel-preset-expo の変換結果を開発・本番で並べ、置き換わる書き方と残ってしまう書き方を表にしました。