◉RORK LABEN
●EXPO — EAS Observe がネイティブクラッシュも記録(10/07)。SDK 57 は 57.0.21 以降で確認できます●SDK 58 — SDK 58 Beta は 9/15 公開。stable の日付はまだ未確認です●RN 0.88 — React Native 0.88.x の正式リリース予定は 10/12、残り3日●Q&A — expo-widgets が本番ビルドだけ真っ黒になる、という問いが出ています●RORK — 09-29 に GPT-6.1 Sol を追加。Pro・Max プランで利用できます●NEW — クライアントのアプリを作る前に決める、公開アカウントの持ち主●EXPO — EAS Observe がネイティブクラッシュも記録(10/07)。SDK 57 は 57.0.21 以降で確認できます●SDK 58 — SDK 58 Beta は 9/15 公開。stable の日付はまだ未確認です●RN 0.88 — React Native 0.88.x の正式リリース予定は 10/12、残り3日●Q&A — expo-widgets が本番ビルドだけ真っ黒になる、という問いが出ています●RORK — 09-29 に GPT-6.1 Sol を追加。Pro・Max プランで利用できます●NEW — クライアントのアプリを作る前に決める、公開アカウントの持ち主
記事一覧/アプリ開発
◇ アプリ開発/2026-10-09初級

Expo Go で通知だけが届かない夜に、先に切り分ける3点と development build への最短の移り方

Android 実機の Expo Go でプッシュ通知が届かないとき、端末設定を疑う前に確かめる3点と、development build に移る最小の手順をまとめました。通知以外に Expo Go で試せないものの見分け方も添えます。

Expo Go3プッシュ通知20development buildEAS Build20トラブルシューティング81

通知の文面を試そうと、Android の実機で Expo Go を開いた夜のことです。送信ツールの画面には「送信済み」と出ているのに、端末はいつまでも沈黙したままでした。

最初に疑ったのは端末の側でした。通知の許可を見直し、省電力モードを切り、Wi-Fi をモバイル回線に替えました。それでも届きません。気づけば 1 時間ほどが過ぎておりました。

原因は端末にも、送信の書き方にもありませんでした。Expo Go という道具そのものが、リモートプッシュを受け取れる作りではなくなっていたのです。昨日まで動いていた手順が今日は通らないとき、手元より先に道具の仕様を見る——この順番を、私はこの夜に決め直しました。

いま思えば、1 時間を溶かした本当の原因は、疑う順番を決めていなかったことだったのかもしれません。

同じところで手が止まっている方に向けて、確かめる順番と、development build へ移る最小の手順を書き残します。

最初にお伝えしたいのは「Android の Expo Go では、リモートプッシュは対象外」という点です

Expo のドキュメントでは、SDK 53 以降、Android の Expo Go ではリモートのプッシュ通知が使えない旨が案内されています。通知を本気で試したい場合は development build を使う、というのが公式の道筋です。

iOS の Expo Go については、私の手元では検証しておりません。ここは公式 FAQ の最新の記述を優先してください。バージョンごとに扱いが変わりうる箇所だからです。

大切なのは、「届かない」という症状が 3 つの別々の原因から生まれうることです。

症状疑う場所見分け方
送信ツールが成功を返すが、端末に何も出ない実行環境(Expo Go かどうか)アプリ内で実行環境を表示して確かめる
トークンの取得で例外が出るprojectId/権限/Android の資格情報例外メッセージの文言を読む
development build では届くが、本番ビルドだけ届かないビルドごとの資格情報(FCM/APNs)EAS の credentials 画面で対象ビルドの設定を見る

1 行目だけが、Expo Go に固有の症状です。残りの 2 行は development build に移ったあとにも出ますので、先に切り分けておく価値があります。

切り分け①:いま動いているのが Expo Go かどうかを、コードに言わせます

端末の見た目で判断すると、思い込みが入ります。アプリ自身に実行環境を答えさせるほうが確実です。

// ExecutionEnvironmentBadge.tsx
// 何を解決するか: 「いま Expo Go で動いているのか」を画面で確定させる
import Constants, { ExecutionEnvironment } from 'expo-constants';
import { Text, View } from 'react-native';
 
export function ExecutionEnvironmentBadge() {
  // StoreClient が Expo Go を指します(bare / standalone と区別されます)
  const isExpoGo =
    Constants.executionEnvironment === ExecutionEnvironment.StoreClient;
 
  return (
    <View style={{ padding: 8 }}>
      <Text>
        実行環境: {isExpoGo ? 'Expo Go' : 'development build / 本番ビルド'}
      </Text>
    </View>
  );
}

なぜこう書くかというと、Constants.appOwnership のような古い判定は将来の版で消えることがあるからです。実行環境の列挙値で見ておけば、判定の根拠を公式の型に預けられます。

画面に「Expo Go」と出たなら、通知の設定を探し回る必要はありません。道具を替えるだけです。

切り分け②:トークン取得の例外は、握りつぶさずに画面へ出します

development build に移っても、トークンが取れない場面はあります。取得の関数は、失敗の理由を文言で教えてくれます。握りつぶすと、その文言を読み損ねます。

// registerForPush.ts
// 何を解決するか: 権限・projectId・Android チャンネルの3つを1か所で確認する
import * as Notifications from 'expo-notifications';
import Constants from 'expo-constants';
import { Platform } from 'react-native';
 
export async function registerForPush(): Promise<string> {
  if (Platform.OS === 'android') {
    // Android 8 以降は、チャンネルが無いと通知が表示されません
    await Notifications.setNotificationChannelAsync('default', {
      name: 'default',
      importance: Notifications.AndroidImportance.DEFAULT,
    });
  }
 
  const current = await Notifications.getPermissionsAsync();
  let status = current.status;
  if (status !== 'granted') {
    status = (await Notifications.requestPermissionsAsync()).status;
  }
  if (status !== 'granted') {
    throw new Error('通知の許可が得られていません(端末の設定から許可が必要です)');
  }
 
  const projectId =
    Constants.expoConfig?.extra?.eas?.projectId ??
    Constants.easConfig?.projectId;
  if (!projectId) {
    throw new Error('projectId が見つかりません(eas init を実行したか確認してください)');
  }
 
  const { data } = await Notifications.getExpoPushTokenAsync({ projectId });
  return data; // ExponentPushToken[...] の形で返ります
}

呼び出し側では try / catch で受けて、メッセージをそのまま画面に出してください。ログだけに流すと、実機を片手にターミナルへ視線を往復させることになります。手が止まる時間は、ここでいちばん長くなります。

Android の場合、トークンの取得そのものは通っても、実際の配信には FCM の資格情報が必要です。この設定は EAS の credentials から行います。

切り分け③:ローカル通知で、端末側の経路を先に確かめます

リモートプッシュは、Expo のサーバーと FCM/APNs を経由する長い道のりです。一方、端末内で予約するローカル通知は、その道のりを通りません。

// scheduleLocalProbe.ts
// 何を解決するか: 端末の許可とチャンネルが生きているかを、サーバー抜きで確認する
import * as Notifications from 'expo-notifications';
 
export async function scheduleLocalProbe() {
  await Notifications.scheduleNotificationAsync({
    content: { title: '通知の疎通確認', body: '5秒後に出れば端末側は健全です' },
    trigger: {
      type: Notifications.SchedulableTriggerInputTypes.TIME_INTERVAL,
      seconds: 5,
    },
  });
}

5 秒後に表示されれば、端末の許可とチャンネルは生きています。疑う範囲を「サーバーから先」に絞れます。表示されないなら、許可かチャンネルが先です。

確かめる場所は、近いほうから順に。 端末→アプリ→サーバーという順を守るだけで、迷う時間はずいぶん減りました。

development build に移る最小の手順

ここまでで「Expo Go が原因」と確定したら、移ります。一度作れば、日々の開発は Expo Go とほぼ同じ手触りで続けられます。

# 1) 開発用クライアントを入れます
npx expo install expo-dev-client
 
# 2) EAS にログインしてプロジェクトを紐づけます(未実施なら)
npx eas-cli login
npx eas-cli init
 
# 3) 開発用ビルドを作ります(Android 実機に入れる例)
npx eas-cli build --profile development --platform android

eas.json には、開発用のプロファイルを足します。

{
  "build": {
    "development": {
      "developmentClient": true,
      "distribution": "internal",
      "android": { "buildType": "apk" }
    },
    "production": {}
  }
}

distribution: "internal" と buildType: "apk" の組み合わせにしているのは、ストアを通さずに実機へ直接入れられるからです。ビルドが終わると QR とリンクが出ますので、実機で開いてインストールします。

入れたあとの起動は、これまでと少しだけ違います。

# development build に接続する形で開発サーバーを起動します
npx expo start --dev-client

通知以外にも、Expo Go では試せないものがあります

通知で一度詰まった方は、同じ壁が別の機能でも出ることを知っておくと楽です。Expo Go に同梱されていないネイティブモジュールを足したとき、あるいはアプリ固有のネイティブ設定(URL スキームや権限の文言など)を確かめたいときは、同じく development build が必要になります。

個人開発でアプリを何本も育てていると、「この機能は Expo Go で足りるのか」を最初に決めておくほうが、後の手戻りが小さいと感じます。私自身は、通知・課金・独自のネイティブ設定のどれかに触れる日は、最初から development build で始めております。

なお、ログイン周りで Expo Go が開かなくなった場合は、Expo Go がプレビューを開かなくなった日に、最初に確かめる2つのログインで別の切り分けをお伝えしています。

次の一歩

今日の作業は、1 つだけで十分です。ExecutionEnvironmentBadge を、使っているアプリの設定画面に 1 行足してみてください。画面に「Expo Go」と出たら、通知の設定を探すのをやめて、npx expo install expo-dev-client から始めていただければと思います。

届かない通知を前にして、疑う順番を先に決めておく。その線引きだけは、これからも守るようにしています。

シェア

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

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

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

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

関連記事

◇ アプリ開発2026-09-02
eas.json に image を書かないままだと、SDK を上げた日に Xcode も pnpm も一緒に動きます
eas.json に image を書いていないビルドの既定は auto です。SDK エイリアスが1つ動くと Xcode・Node.js・pnpm・fastlane がまとめて入れ替わります。Xcode 27 が来る前に、自分のビルドが通ったイメージ名をログから拾い、固定するかどうかを決めるまでの手順です。
⬡ 開発ツール2026-09-21
exit code 0 で止まるビルド — ログを黙らせていた設定と、空ファイルを通した存在チェック
eas build --local が exit code 0 but produced no further output で止まる件と、0 バイトのスタブが埋め込まれず dyld で落ちる件。どちらも原因は出力を減らす設定でした。自分のスクリプトを棚卸しする手順まで書き残します。
◇ アプリ開発2026-09-29
起動のたびに一瞬ログイン画面が出る expo-router アプリを、Protected routes と「まだ分からない」の三状態で組み直す
ログイン済みなのに起動直後にログイン画面が一瞬出る、ログアウトしても画面が変わらない。expo-router の Protected routes に Supabase の認証状態を「不明」込みの三つで渡し、_layout.tsx の置き場所まで含めて直した手順です。
📚RECOMMENDED BOOKS
大規模言語モデル入門
山田育矢
LLM開発
生成AIプロンプトエンジニアリング入門
我妻幸長
プロンプト
Claude CodeによるAI駆動開発入門
平川知秀
AI駆動開発
※ アフィリエイトリンクを含みます