アプリが動作するようになったら、ユーザーがサインインした後、製品が再エンゲージフローのようなネイティブな感覚を求めるようになります。 カートのリマインダー。 レビューの誘導。 新しいメッセージのアラート。 リリースのアナウンス。 最初のインスピレーションは、「プッシュを簡単に組み立てる」ということです。 ただし、1週間後には、1台のデバイスがアラートを受け取るのに対して、シミュレーターは正常に登録されているように見え、誰も説明できないようにタップが正しい画面を開くことができないことがわかります。
エクスポ推送通知は、単純で魅力的なものか、驚くほど脆弱なものかです。
エクスポは、APNsとFCMの上位レイヤーを提供するReact Nativeチームに実用的で便利なものを提供します。これが、多くのチームが使用する理由です。 ただし、デモと生産性のある実装の間のギャップは実際に存在します。 トークンライフサイクル、パーミッションのタイミング、リスナー設定、ペイロードの設計、バックエンドのクリーンアップなど、すべての要素が重要です。 さらに頻繁にアプリロジックを変更している場合、信頼性の高いメッセージングとリリースの速度が依存する場合、オペレーショナルディシプリンの必要性はさらに強調されます。 これは、信頼性の高いメッセージングとリリースの速度が依存する場合、オペレーショナルディシプリンの必要性がさらに強調されることと同じより広い懸念です。 モバイル アプリユーザーロイヤルティー作業:ユーザー体験の周りで予測可能である場合にのみ、配信は有用です。
目次
- ユーザーとのつながりを築くためのエクスポ プッシュ通知の基盤
- プロジェクトの初期設定と構成
- パーミッションの要求とプッシュトークンのキャプチャ
- サーバーから通知を送信する
- アプリ内で受信した通知を処理する
- 実稼働のベストプラクティスと一般的な落とし穴
Expo Push Notificationとユーザーの関与の基礎
An Expo Push Notification セットアップは、1つの理由で魅力的なものです。複雑なネイティブメッセージングをチームが最初の日から所有したくないため、多くのネイティブメッセージングの複雑さを削減します。APNsとFCMの直接プラumbingを最初に構築するのではなく、Expoのゲートウェイと協力して、製品の動作、ルーティング、許可のUX、バックエンドのメッセージロジックに焦点を当ててください。
アブストレクションは、Pushが「実際に」なり得ないことを意味するものではありません。ただし、エンジニアリングの努力の場所が変わります。
サービスは、パフォーマンスが心配されることよりも早く動作します。2023年3月14日から2023年6月12日までの期間、ExpoのPush Notification APIは、 42ミリ秒のメディアン応答時間, 273ミリ秒のp99遅延、そして 0.17%の平均日間エラー率 、 CapawesomeのCapgoとの比較ページ。役割:長いマーケティングまたは法的文章。見つける場所:capwesome.astro数百万の日間メッセージ Knock’s Expo push API benchmark analysisKnockのExpoプッシュ__CAPGO_KEEP_0__ベンチマーク分析
。それが実際に抽象化するものは
チームが「Expoプッシュ」と言っている場合、多くの場合、複数の別々の懸念をまとめて言っているのです:
- ルーティングプロバイダー: Expoはメッセージを APNs iOS と Android の両方に対して FCM Android の場合。
- トークン形式: サーバーは、プラットフォーム固有のトークン管理を最初に管理するのではなく、Expo Push Token を保存して送信します。
- リクエスト契約: Expo のプッシュ API へのペイロードを POST するのではなく、ネイティブ プロバイダー API に直接統合するのではなくします。
実際には、多くの失敗は、古いトークン、不正なペイロード、または不十分な許可フローなど、アプリケーション code の問題から来ています。
実践的なルール: Expo を信頼できるトランスポート層として扱い、クライアントとバックエンドの設計に問題がないことを確認すること。
実際の生産性とは何であるか
動作するデモは、1 つのデバイスが 1 回のペイロードを受け入れたことを証明するだけです。生産性は別の意味を持ちます:
| 問題 | デモの考え方 | 実稼働の考え方 |
|---|---|---|
| 許可 | すぐに質問する | ユーザーの価値が明確になるまで、コンテキスト内で質問する |
| トークン | 一度だけ保存 | 更新、重複排除、期限切れ、調整 |
| ペイロード | すべてを含める data |
ペイロードを小さく、行動指向的にする |
| アプリの動作 | アラートを表示 | 正しくナビゲートし、前景状態を処理 |
| オペレーション | 手動テスト | 受領、クリーンアップ、ログ、インシデントの処理 |
「通知を送信する」と「通知を実際の製品フローでサポートする」というのはどちらが違うのか?
プロジェクトの初期設定と構成
Expo Push の多くの痛みは、最初の許可の促し方よりも前に始まる。プロジェクトの構成が雑になっていると、クライアント code が正しく見えているのに、アプリがビルドごとに一貫して動作しないことがある。

正しいライブラリがインストールされ、開発環境がビルドパスと一致している状態から始めましょう。Expo Go を超えて作業している場合は、ローカルワークフローをカスタム Expo 開発クライアントのセットアップと合わせることが役に立つでしょう custom Expo development client setup、通知の動作がビルドで検証される必要があるのは、実際の生産環境に近いものが必要だからです。
通知パッケージをインストールする
最低限、許可の要求、トークン取得、リスナー、通知の表示が必要です。
expo-notificationsトークン取得を守るためにexpo-deviceインストールコマンドはパッケージマネージャーに依存しますが、主なことはエクスポのバージョンと一致させることです。__CAPGO_KEEP_0__とエクスポのバージョンを混ぜることは避けましょう。エクスポが互換性のあるバージョンを解決するようにします。Device.isDevice.
Typical install commands depend on your package manager, but the key is version alignment with your Expo SDK. Don’t mix arbitrary package versions. Let Expo resolve compatible ones.
設定を明示的に行いましょう。最小限の
または app.json 、 app.config.js エクスポのバージョンと一致させることが重要です。__CAPGO_KEEP_0__とエクスポのバージョンを混ぜることは避けましょう。エクスポが互換性のあるバージョンを解決するようにします。
{
"expo": {
"name": "MyApp",
"slug": "my-app",
"plugins": ["expo-notifications"],
"ios": {
"bundleIdentifier": "com.example.myapp"
},
"android": {
"package": "com.example.myapp"
},
"extra": {
"eas": {
"projectId": "your-project-id"
}
}
}
}
プロジェクトのレベルで設定を追加する
- 設定を明示的に行いましょう。最小限の 実際に配信するアプリと一致させる必要があります。
- 通知プラグイン ネイティブプロジェクトがビルド時に必要な設定を取得することを保証します。
- EAS プロジェクト ID トークン取得の際にアプリが正しい Expo プロジェクトと関連付けられていることを期待している場合に重要です。
通知ハンドラーを早く設定する
基本的なチュートリアルでは、通知の動作を定義するのを遅すぎます。なぜなら、foreground の動作が予測可能になるからです。アプリ起動時に近く設定してください。
import * as Notifications from 'expo-notifications';
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldShowAlert: true,
shouldPlaySound: false,
shouldSetBadge: true,
}),
});
チームは、foreground の通知がアラートを表示するか、サウンドを再生するか、バッジを変更するかを決定します。製品によっては、同じ決定を下す必要があります。チャットアプリと決済アプリは同じ決定を下す必要はありません。
foreground の動作を意図的に定義しないと、チームは「見えにくい」通知をデバッグすることになりますが、実際には受信した通知が製品の期待どおりに表示されていませんでした。
Android ではチャネル設定が必要です
Android の通知チャネルは実用上は必須です。チャネルを省略すると、アラートが不一致またはユーザーの期待に合わない可能性があります。
import { Platform } from 'react-native';
import * as Notifications from 'expo-notifications';
export async function configureAndroidNotifications() {
if (Platform.OS !== 'android') return;
await Notifications.setNotificationChannelAsync('default', {
name: 'Default',
importance: Notifications.AndroidImportance.MAX,
});
}
アプリ起動時にこの設定を実行し、チャネル ID を安定させます。チャネル ID を頻繁に変更すると、通知の動作を推論することが難しくなります。
パーミッションの要求とプッシュ トークンのキャプチャ
多くのチームはスニペットからコピーし、最終的には後悔することになる部分です。
パーミッションの要求にはタイミング、プラットフォームの認識、厳格な非同期ハンドリングが必要です。トークンのキャプチャは、物理デバイス上で実行され、パーミッションが解決された後、バックエンドに結果を保存する準備ができた場合にのみ行う必要があります。

クライアント関数が基本的な基準
このような関数を使用して、プロセスの開始点として使用してください。
import * as Device from 'expo-device';
import * as Notifications from 'expo-notifications';
import Constants from 'expo-constants';
import { Platform } from 'react-native';
type RegisterResult =
| { ok: true; token: string }
| { ok: false; reason: string };
export async function registerForExpoPushNotificationsAsync(): Promise<RegisterResult> {
if (!Device.isDevice) {
return { ok: false, reason: 'Push notifications require a physical device.' };
}
if (Platform.OS === 'android') {
await Notifications.setNotificationChannelAsync('default', {
name: 'Default',
importance: Notifications.AndroidImportance.MAX,
});
}
const permissions = await Notifications.getPermissionsAsync();
let finalStatus = permissions.status;
if (finalStatus !== 'granted') {
const request = await Notifications.requestPermissionsAsync();
finalStatus = request.status;
}
if (finalStatus !== 'granted') {
return { ok: false, reason: 'Notification permission was not granted.' };
}
const projectId =
Constants.expoConfig?.extra?.eas?.projectId ??
Constants.easConfig?.projectId;
if (!projectId) {
return { ok: false, reason: 'Missing EAS project ID configuration.' };
}
const tokenResponse = await Notifications.getExpoPushTokenAsync({ projectId });
return { ok: true, token: tokenResponse.data };
}
順序は重要です。デバイスの種類を確認し、Android チャンネルの動作を設定し、パーミッションを解決し、プロジェクトの構成を検証し、最後にエクスポ トークンを要求します。
なぜ Device.isDevice は省略できない
これは、見た目は無害に見えるが、多くのノイズを生み出す一部の間違いです。専門チームは、 Device.isDevice が真である場合にのみパーミッションを要求し、を省略すると、開発者は無効のシミュレータ トークンに通知を送信し、エクスポの問題であると誤解を招くことになります。 EagerworksのExpo通知実装ノート.
なぜチェックは関数の先頭にあるのか。ヘルパーに隠すな。明らかでなければなりません。
シミュレータの結果はUIテストに役立ちますが、プッシュトークンの登録を検証するのに信頼できません。
許可を求める時期を選ぶ
スプラッシュ画面では許可を求めない。ユーザーが価値を理解する前に許可を求めない。通常、ユーザーがアクションを実行し、通知の利点が具体的になるまでに許可を求めるのが一番よいタイミングです。たとえば、配信更新の有効化、会話への参加、またはウォッチしたアイテムの保存など。
良い実装は通常このフローを遂行します:
- ユーザーが意味のある機能境界に到達します。
- アプリが通知の価値を独自のUIで説明します。
- アプリがシステムの許可を求めます。
- アプリが許可が付与された場合にすぐにバックエンドでトークンを保存します。
最後のステップで多くのアプリが失敗します。トークンを取得し、ローカルにログし、バックエンドの登録を延期します。後で、どのデバイスがどのトークンを持っていたのか、どの時点でどのトークンを持っていたのかを判断するのが難しくなります。
トークンの登録後にトークンを保存する簡単な例はこちらです
export async function enablePushForCurrentUser(userId: string) {
const result = await registerForExpoPushNotificationsAsync();
if (!result.ok) {
return result;
}
await fetch('https://api.example.com/push-tokens', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer user-session-token',
},
body: JSON.stringify({
userId,
token: result.token,
platform: Platform.OS,
}),
});
return result;
}
後続のワークフローで、このガイドは、視覚的な参照として役立ちます。
リリース重視のアプリを開発するチームにとって、トークン登録はアプリの運用状態の一部であると考えることも役立ちます。アプリのオンボーディングの部分だけではありません。そうした考え方は、より広範な Expoアプリ配信ワークフロー、とよく合致します。アプリの挙動は頻繁に変わり、バックエンドの状態は常に同期する必要があります。
サーバーから通知を送信する
バックエンドに有効なExpo Push Tokenが得られたら、通知を送信することは簡単です。リクエスト自体が難しい部分ではありません。payloadの内容とクライアントの状態に置ける信頼の度合いが問題です。
Nodeスタイルの最小限の例 fetch:
type ExpoPushMessage = {
to: string;
title: string;
body: string;
sound?: 'default' | null;
data?: Record<string, unknown>;
};
export async function sendExpoPushNotification(token: string) {
const message: ExpoPushMessage = {
to: token,
title: 'New review received',
body: 'Tap to open the order details.',
sound: 'default',
data: {
type: 'new_review',
orderId: 'ord_123',
screen: 'OrderDetails',
},
};
const response = await fetch('https://exp.host/--/api/v2/push/send', {
method: 'POST',
headers: {
Accept: 'application/json',
'Accept-encoding': 'gzip, deflate',
'Content-Type': 'application/json',
},
body: JSON.stringify(message),
});
const result = await response.json();
return result;
}
payloadの各フィールドの役割
payloadを捨て場と考えるのはやめましょう。各フィールドは意図的に使いましょう。
| フィールド | 目的 | コンテキスト:Capgoマーケティングウェブサイト。役割:短いUIラベルまたはナビゲーションアイテム。メッセージキーsubprocessors_table_purpose(Subprocessors Table Purpose)。 |
|---|---|---|
to |
__CAPGO_KEEP_0__ | __CAPGO_KEEP_0__ |
title |
__CAPGO_KEEP_0__ | 通知ヘッダー |
body |
__CAPGO_KEEP_0__ | __CAPGO_KEEP_0__ |
sound |
__CAPGO_KEEP_0__ | __CAPGO_KEEP_0__ |
data |
__CAPGO_KEEP_0__ | __CAPGO_KEEP_0__ |
アプリ固有のメタデータ data そのオブジェクトは、製品ワークフローで役に立つ場所です。型とレコードIDを渡すことができ、ユーザーがタップすると最新のデータを取得できるようになります。そうすることで、大きなまたは敏感なブロブを直接ペイロードに埋め込むのではなく、より安全になります。
ペイロードを小さく、面白くないものにしましょう。
Capgoによると CourierのExpo通知のガイド, Expo Push Tokensは、ephemeralとみなされます。 roughly 4 KBのサイズ制限を超えるペイロードは、ドロップされる可能性があり、信頼できるパターンは、小さなメタデータペイロードを送信することです。 例えば、JSONやインラインメディアではなく、 実際のシステムでは機能するアドバイスと一致しています。 小さなペイロードは、より少ない失敗率と、アプリロジックが変更されたときに長く持続することができます。 ユーザーをルーティングするのに十分なデータを送信してください。 アプリが開いた後、残りのデータを取得してください。 { "type": "new_review", "id": 123 } 有用なサーバーサイドの習慣
テスト用の基本的な送信関数は十分ですが、実用的なものは、さらにいくつかの責任を追加します:
送信試行を永続化してください:
ユーザーID、トークン、ペイロードタイプ、タイムスタンプと共に、通知の意図を保存してください。
- __CAPGO_KEEP_0__ __CAPGO_KEEP_0__
- コンテンツ生成とトランスポートを分離する メッセージのコピーを1層で作成し、Expo API リクエストを別の層で作成する
- 無効化のフィードバックを処理する Expoが後で報告した場合
DeviceNotRegistered, そのトークンを古いものとしてマークし、無条件にリトライを停止する - Webhookに適した設計を使用する システムがすでにイベントを発行している場合、通知トリガーを同じ種類の バックエンドWebhook処理パターン 他の場所で使用している
クライアントリスナをデバッグする前に、手動テストプッシュを送信する。トークンが小さなペイロードを持つ単純な通知を受け取った場合、トランスポートパスは正常であるはずです。そうでない場合は、ナビゲーションcodeを変更するのではなく、トークン、ペイロードの形状、許可状態を検証することから始める
Incoming Notificationsを処理する
配信は機能の半分だけです。通知が到着したときとユーザーがタップしたときにアプリが何か意味のあることを行う必要があります
2つの異なる時点を処理する必要があります:
- アプリが開いているときに通知が届く
- システムトレイまたはロック画面からユーザーが通知に触れる

フォアグラウンド受信とユーザーからの反応は異なるイベント
信頼性のあるセットアップには両方のリスナーが含まれます:
import { useEffect } from 'react';
import * as Notifications from 'expo-notifications';
export function useNotificationObservers(
onForegroundMessage: (notification: Notifications.Notification) => void,
onNotificationTap: (response: Notifications.NotificationResponse) => void
) {
useEffect(() => {
const receivedSub = Notifications.addNotificationReceivedListener(
(notification) => {
onForegroundMessage(notification);
}
);
const responseSub = Notifications.addNotificationResponseReceivedListener(
(response) => {
onNotificationTap(response);
}
);
return () => {
receivedSub.remove();
responseSub.remove();
};
}, [onForegroundMessage, onNotificationTap]);
}
addNotificationReceivedListener アプリがアクティブなときに実行される addNotificationResponseReceivedListener ユーザーが通知にタップしたときに実行される。組み合わせて考えるのではなく、異なるUXパスを提供する
データペイロードを読み取り、意図的にナビゲートする
タップハンドリングの実践的なパターン
type NotificationData = {
type?: string;
orderId?: string;
screen?: string;
};
export function handleNotificationTap(
response: Notifications.NotificationResponse,
navigation: any
) {
const data =
response.notification.request.content.data as NotificationData;
if (data.screen === 'OrderDetails' && data.orderId) {
navigation.navigate('OrderDetails', { orderId: data.orderId });
return;
}
if (data.type === 'new_review') {
navigation.navigate('Inbox');
return;
}
navigation.navigate('Home');
}
パターンは、ルーティングヒントが含まれているペイロードのため、頑丈です。通知が送信されたときに順序が変更されていても、ナビゲーション後、現在のサーバー状態を取得できます。
タップされた通知は、明らかな目的地に到達するはずです。フォールバックパスが曖昧であれば、ユーザーはすぐに気づきます。
前景動作はユーザー コンテキストに合わせるべきです
アプリがすでに開いている場合、システムスタイルのアラートを無視して表示すると、不快な印象を与える可能性があります。時々、適切なアクションはインアプリバナー、バッジの更新、または静的なリフレッシュです。サポートのインボックス画面では、ユーザーがすでにその会話を読んでいる場合、可視的なアラートが必要ない場合があります。
そのため、前景リスナーはルートと通知タイプによってbranchするべきです。たとえば:
- チャット画面が開いている場合: メッセージを追加し、冗長なバナーを避けます
- ダッシュボードが開いている場合: 軽量なインアプリトーストを表示します
- 重要なアカウントイベントの場合: 強力なUI処理を表面化します
シンプルなアプローチは次のようになります:
export function handleForegroundNotification(
notification: Notifications.Notification,
currentRouteName: string
) {
const data = notification.request.content.data as { type?: string };
if (currentRouteName === 'ChatThread' && data.type === 'new_message') {
// refresh local thread state
return;
}
// otherwise show your own in-app UI or update badges
}
アプリがこれらのコンテキストを区別しない場合、ユーザーは通知疲れを感じる速度が早くなります。即ち、技術的に正しい配信でもあります。
生産性の向上と共通の誤り
Expoのプッシュ通知設定の多くが、Expoが機能が制限されているため、機能しないというわけではありません。
実際は、チームがトークンが永久的なものであると仮定し、ペイロードに何でも入れることができ、更新後も通知ロジックに影響がないと考えているため、失敗することが多いです。

トークンは、ユーザーIDのレコードではなく、短期的なものです。
Expo Push Tokenは、ライセンスのように扱うべきであり、生涯にわたるデバイスIDとして扱うべきではありません。 DeviceNotRegisteredトークンは、再インストール、OSの変更、ライフサイクルイベントなどによって回転することがあります。
トークンが最終的に戻ってきた場合、バックエンドではそれを有効として扱うべきではありません。
- 実用的なバックエンドモデルでは、以下の情報を保存します。
- ユーザーID
- プラットフォーム
- インストールスコープのメタデータ
- 現在のトークン
- 状態は有効、古い、または取り消されたなど
ユーザー情報テーブルにトークンフィールドを1つだけ保存して終わりはしないでください。ユーザーは複数のデバイスを持っており、デバイスの状態は変化します。
リフレッシュ戦略は、ほとんどのチュートリアルが認めていないほど重要です。
公式のエコシステムガイドラインは、実際の運用上のギャップを残しています。既存のExpoプッシュ通知のコンテンツは、App StoreのレビューサイクルとOTAアップデートの間でトークンの有効性を維持する方法を説明していません。 特に、ライブ変更を実装しているチームにとっては、プッシュ通知の信頼性は、現在のトークンの状態とバックエンドの同期に依存しているため、重要です。これはExpo通知ドキュメントで説明されています。これは、リフレッシュトリガーの設計に影響を与えることです。トークンの状態を整合させる良いタイミングは次のとおりです。 アプリの更新後に起動する.
ユーザーがサインインする
- パーミッション設定が変更される
- クレデンシャルローテーションがリリースプロセスで実行される
- Permission settings change
- Credential rotation work on your release process
- Push通知関連のサポートチケットの後処理
セキュリティとコンプライアンスはスプリントの最後に属さない
多くのExpoチュートリアルは、メカニズムに焦点を当てて、運用上のリスクを省略しています。それは趣味アプリ用には問題ありませんが、医療関連、金融技術、規制された商業製品では問題があります。
CourierのExpo通知のギャップに関するエンタープライズ向けの議論 は、consentログ、監査トレイル、敏感なペイロードの露出を最小限に抑える実践的なガイダンスの欠如を強調しています。直接的なエンジニアリングの取り組みは単純です。
- 通知テキストまたはペイロードメタデータに敏感なビジネスデータを含めないでください。
- consentの変更をサーバー側でログしてください。
- どのトークンにどの通知の意図が送信されたかを記録してください。
- ペイロードにIDを使用し、アプリを開いた後、保護されたコンテンツを取得してください。
リリースオペレーションをより広範な アプリストアのコンプライアンスとAPI セキュリティの実践と同期するチームでは、Pushは認証、分析、バックエンドイベントログと同じレビューの範囲に含めるべきです。__CAPGO_KEEP_0__
ユーザーフェイスのメッセージですが、分散システムの問題でもあります。認証状態や支払イベントと同じ注意を払ってください。
通常どれが機能し、どれが機能しないか
| 通常機能する | 許可の申請は明確な値の説明の後に |
|---|---|
| 最初のフレームで提示する | 実機でテストする |
| シミュレータの登録を信頼する | デバイスのコンテキストと共にトークンを保存する |
| ユーザーレコードごとに1つのトークン | 小さなメタデータのペイロードを送信する |
| 大きなまたは敏感なブロブを埋め込む | __CAPGO_KEEP_0__ |
| 前景とタップイベントを個別に処理する | すべての通知が一つのパスを遷移することを前提としている |
| 古いトークンを積極的に期限切れにする | 死んだトークンを永久にリトライする |
実際には、CapgoのExpoプッシュ設定は複雑ではない。 それは、規律が必要です。
あなたのチームが頻繁にアプリロジックを変更し、リリースの動作、ロールバック、配信の可視性をより制御したい場合、Capgoはおすすめです。 Capgo チームがモバイルアプリの更新を迅速に配信したい場合、Capgoは有効な選択肢です。 これは、特に通知フローの変更、ルーティングロジック、クライアントサイドの修正がユーザーに迅速に到達する必要がある場合に、特に有効です。