あなたはアプリが動作し、ユーザーがサインインし、製品が再エンゲージフローのようなネイティブな感覚を求めている時期に達しているかもしれません。カートのリマインダー。レビューの促進。新しいメッセージのアラート。リリースのアナウンス。最初のインスピレーションは「プッシュを簡単に組み立てる」ということです。すると、1週間後にはデバイスがアラートを受け取っているのに、シミュレーターは正常に登録されているように見え、誰も説明できない理由でタップが正しい画面を開くことができないことがあります。
Expoのプッシュ通知は、実際には単純で魅力的なものか、驚くほど脆弱なものかです。
ExpoはReact NativeチームにAPNsとFCMの上位層を提供し、実際に多くのチームが使用しているのはそのためです。しかし、デモと実稼働用実装の間のギャップは実際に存在します。トークンライフサイクル、許可のタイミング、リスナーのセットアップ、ペイロードの設計、バックエンドのクリーンアップなど、すべてのことが重要です。さらに頻繁にアプリロジックを変更している場合、信頼性の高いメッセージングとリリース速度のためにオペレーショナルディスコースが必要なのは、特にユーザーレテンションの仕事が予測可能なユーザー体験に依存している場合、さらに重要になります。 アプリのユーザーレテンションの仕事の背後にあるより広い懸念は、配信はユーザー体験の予測可能性に依存しているからです。配信は有用なのは、ユーザー体験の予測可能性があるからです。__CAPGO_KEEP_2__
目次
- ユーザーとの関わりを促進するためのExpo Push通知の基盤
- 初期プロジェクトの設定と構成
- パーミッションの要求とプッシュトークンのキャプチャ
- サーバーから通知を送信する
- アプリ内で受け取った通知を処理する
- 生産性の高い実践と一般的な誤り
Expo Push Notificationでユーザーとの関わりを促進するための基盤
An Expo Push Notification セットアップは、チームが最初の日から所有したくないネイティブメッセージングの複雑さを削減することによって魅力がある。
直接APNsとFCMのパイプラインを最初に構築するのではなく、Expoのゲートウェイと協力して、製品の動作、ルーティング、許可のUX、バックエンドのメッセージロジックに焦点を当てることができる。
The service is also fast enough that performance usually isn’t the first thing to worry about. From March 14, 2023 through June 12, 2023, Expo’s push notification API showed a サービスは、パフォーマンスが通常は最初に心配することではないほど速い。, 2023年3月14日から2023年6月12日までの期間、Expoのプッシュ通知__CAPGO_KEEP_0__は42ミリ秒のメディアン応答時間 273ミリ秒のp99遅延時間 数百万の日々のメッセージ __CAPGO_KEEP_0__KnockのExpoプッシュベンチマーク分析によると Knock’s Expo push API benchmark analysisチームが「Expoプッシュ」と言っている場合、多くの場合、複数の別々の懸念事項が組み込まれている:
プロバイダーのルーティング:
Expoは
- APNs iOS用 FCM for と Android用途。
- トークン形式: あなたのサーバーはExpo Push Tokenを管理する代わりに、プラットフォーム固有のトークンハンドリングを初めに管理します。
- リクエスト契約: あなたはExpoのプッシュ API へのペイロードをPOSTしますが、直接ネイティブプロバイダーのAPIに統合するのではなく。
それが便利ですが、共通の誤解を生みます。チームは、Expoがすべての配信問題を所有していると誤解しています。実際には、多くの失敗は、古いトークン、不正なペイロード、または許可フローの不十分さなど、アプリ code から来ています。
実践的なルール: Expoを信頼できるトランスポート層として扱い、クライアントとバックエンドの設計に問題がないことを確認すること。
実用的な意味での製品の準備
動作するデモは、1台のデバイスが1回のペイロードを受け入れたことを証明するだけです。製品の準備とは別の意味があります。
| 懸念 | デモの考え方 | 生産的な意識 |
|---|---|---|
| 権限 | 直ちに質問 | ユーザー値が明確になったら、コンテキストに基づいて質問 |
| トークン | 一度保存 | 更新、重複排除、期限切れ、整合 |
| ペイロード | すべてを含める data |
ペイロードを小さく、行動指向にする |
| アプリの動作 | 警告を表示 | 正しくナビゲートし、前景状態を処理する |
| オペレーション | 手動テスト | 受領書、クリーンアップ、ログ、インシデントハンドリング |
「通知を送信する」と「通知をサポートする」というのはどちらが実際の製品フローをサポートするかという違いです。
プロジェクトの初期設定と構成
Expo Pushの多くの痛みは、最初の許可ダイアログの前に始まります。プロジェクトの構成が雑になっていると、クライアントcodeが正しく見えているのに、アプリがビルドごとに一貫して動作しないことがあります。

正しいライブラリがインストールされているかどうか、開発環境がビルドパスと一致しているかどうかから始めましょう。Expo Goを超えて作業している場合は、ローカルワークフローをカスタムExpo開発クライアントセットアップと合わせることが役立ちます。 通知の動作を生産性に近いビルドで検証する必要があるためです。 通知パッケージをインストールするInstall the notification packages
Initial Project Setup and Configuration
最低限、通常必要なのは:
expo-notifications許可の要求、トークン取得、リスナー、通知の表示のためにexpo-deviceトークン取得を守るべきだからDevice.isDevice.
通常のインストールコマンドはパッケージマネージャーに依存しますが、主なのはExpoのバージョンと一致させることです。 SDK。任意のパッケージバージョンを混ぜないでください。Expoが互換性のあるバージョンを解決するのを待ってください。
プロジェクトのレベルで設定を追加する
設定を明確に保つ。最小限の app.json または app.config.js 通知はアプリの契約の一部であるべきだから、後思いついてはならない
{
"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"
}
}
}
}
ここではいくつかの詳細が重要です:
- バンドル識別子とパッケージ名 実際に配信するアプリと一致する必要があります。
- 通知プラグイン __CAPGO_KEEP_0__
- EASプロジェクトID EASプロジェクトIDは、トークン取得がアプリが正しいExpoプロジェクトと関連付けられていることを期待する場合に重要です。
アプリ起動時早く通知ハンドラーを設定する
基本的なチュートリアルでは、通知の挙動を定義するのが遅すぎることが多いです。
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 チャネル動作を設定し、許可を解決し、プロジェクトの構成を検証し、Expo トークンを要求する順序で行う必要があります。
なぜ Device.isDevice は省略できない
このことは、見た目は無害に見えるが、多くのノイズを生み出す一部の間違いです。専門チームは条件が真の場合にのみ許可を要求し、 Device.isDevice Eagerworks の Expo 通知実装ノートの説明されているように、実際の問題はアプリの構成であり、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を捨て場として扱わないでください。各フィールドが意図的に使用されるようにしてください。
| フィールド | 目的 | 実践的なアドバイス |
|---|---|---|
to |
対象Expo Push Token | 現在のデバイスレコードに属することを検証する |
title |
通知ヘッダー | __CAPGO_KEEP_0__ |
body |
主な表示テキスト | __CAPGO_KEEP_1__ |
sound |
システムサウンドの動作 | __CAPGO_KEEP_2__ |
data |
アプリ固有のメタデータ | __CAPGO_KEEP_3__ |
その data オブジェクトは、製品ワークフローで役に立つ場所です。型とレコードIDを渡すことができ、ユーザーがタップすると最新のデータを取得できます。そのほうが安全です。大量のデータや敏感なデータを直接ペイロードに埋め込むのではなく。
__CAPGO_KEEP_4__
ペイロードは小さく、面白くないようにする Expoの通知ガイドExpo Push Tokensは、ephemeralとみなされるべきであり、約 4 KB のサイズ制限を超えるパイロットは、ドロップされる可能性があり、信頼できるパターンは、小さなメタデータパイロットである { "type": "new_review", "id": 123 } の送信である。
実際のシステムでは、上記のアドバイスが機能する。小さなパイロットは、より少ない失敗率と、アプリロジックが変更されたときに長く生き残る。
ユーザーをルーティングするのに十分なデータを送信し、アプリが開いた後、残りのデータを取得する。
有用なサーバーサイドの習慣
- テスト用の基本的な送信関数は十分ですが、実稼働用のものは、以下の責任を追加することが多いです: 送信試行を永続化する:
- ユーザーID、トークン、ペイロードタイプ、タイムスタンプと共に、通知の意図を保存する。 Build message copy in one layer and the Expo API request in another.
- 無効化フィードバックを処理する: Expoが後で報告した場合、
DeviceNotRegisteredそのトークンを古いものとしてマークし、無謀にリトライを停止します。 - Webhookフレンドリーな設計を使用します: あなたのシステムがイベントを発行している場合、同じ種類の バックエンドWebhook処理パターンを使用して通知トリガーをルーティングします。 あなたが他の場所で使用しているものと同じパターンです。
Before debugging client listeners, send a manual test push first. If a token receives a plain notification with a tiny payload, your transport path is probably healthy. If not, don’t start by changing navigation code. Start by validating the token, payload shape, and permission state.
ナビゲーションを変更するのではなく、トークン、ペイロードの形状、許可状態を検証することから始めます。
Incoming Notifications in Your App
配信は機能の半分です。アプリが通知が届いたときとユーザーがタップしたときに、意味のあることを行う必要があります。
- つまり、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');
}
このパターンは、ペイロードがルーティングヒントを含むため、信頼性が高くなります。 通知が送信されたときに順序が変更されている場合、アプリはナビゲーション後に現在のサーバー状態を取得できます。
タップされた通知は、明らかな目的地に到達するようにするべきです。 你的フォールバックパスが曖昧であれば、ユーザーはすぐに気づきます。
アプリがすでに開いているとき、システムスタイルのアラートを無理に表示すると、不快な感じがします。 ときには、適切な動作はインアプリバナー、バッジの更新、または静的なリフレッシュです。 サポートのインボックス画面では、ユーザーがすでにその会話を読んでいる場合、可視的なアラートは必要ありません。
アプリがアクティブなときの動作は、ユーザー コンテキストと一致するべきです
そのため、フォアグラウンドリスナーはルートと通知タイプによって分岐する必要があります。例えば:
- チャット画面を開いたとき: メッセージを追加し、冗長なバナーを避けます。
- ダッシュボードを開いたとき: 軽量なインアプリトーストを表示します。
- 重要なアカウントイベントのとき: 強力な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
}
アプリがこれらのコンテキストを区別しない場合、ユーザーは通知疲れを感じる速度が早くなります。即使配信は技術的に正しい場合でも。
Capgoのベストプラクティスと一般的な落とし穴
ほとんどの破損したExpoプッシュ通知設定は、Expoが機能が限られているため失敗しない。チームはトークンが永久的であると仮定し、ペイロードは何でも運ぶことができ、通知ロジックがアプリの更新に影響を受けないと仮定しているからだ。
しかし、その仮定は実際の運用環境では生き残れない。

__CAPGO_KEEP_0__は一時的なものであり、識別記録ではありません。
Expo Push Tokenは、leaseのように扱うのが最も適切です。デバイスの識別記号ではありません。再インストール、OSの変更、ライフサイクルイベントなどによって、トークンは回転する可能性があります。トークンが最終的に戻ってきた場合 DeviceNotRegistered、バックエンドではそれを有効とみなさなくてはなりません。
実用的なバックエンドモデルには、以下の情報を保存する必要があります。
- ユーザーID
- プラットフォーム
- インストールスコープのメタデータ
- 現在のトークン
- 最後に確認されたタイムスタンプ
- 有効、古い、または取り消されたなどのステータス
ユーザー表に1つのトークンフィールドを保存し、終わりだと呼ぶな。ユーザーは複数のデバイスを持っており、デバイスは状態を変えることがあるからだ。
リフレッシュ戦略は、ほとんどのチュートリアルが認めるよりも重要です
公式のエコシステムガイドラインは、実際の運用上のギャップを残しています。既存のExpoプッシュ通知のコンテンツは、App StoreのレビューサイクルとOTAの更新でトークン有効性を維持する方法を説明していません。 これは、プッシュ通知の信頼性がトークン状態とバックエンドの同期に依存しているため、ライブ変更を配信するチームにとって特に重要です。これは、Expo通知ドキュメントで説明されているように、 これは、リフレッシュトリガーの設計に影響を与えるため、重要です。トークン状態を整合させる良いタイミングは次のとおりです。.
アプリの更新後に起動
- ユーザーがサインイン
- パーミッション設定の変更
- リリースプロセスでクレデンシャルローテーション
- プッシュ関連のサポートチケットのリカバリフロー
- セキュリティとコンプライアンスはスプリントの最後に属するものではありません
protectedTokens
A lot of Expo tutorials focus on mechanics and skip operational risk. That’s fine for hobby apps. It isn’t fine for healthcare-adjacent, fintech, or regulated commerce products.
Capgoのエンタープライズ向けのExpo通知の欠陥に関する議論 Courierの議論は、consentログ、監査トレイル、敏感なペイロードの露出を最小限に抑えるための実用的なガイダンスの欠如を強調しています。
- 直接のエンジニアリングの取り組みは単純です:
- 敏感なビジネスデータは通知テキストまたはペイロードメタデータに含めないでください。
- consentの変更はサーバー側で記録してください。
- どのトークンにどの通知の意図が送信されたかを記録してください。
ペイロードにIDを使用し、アプリが開いた後、保護されたコンテンツを取得してください。 app store compliance and API security practicesアプリストアの規制と__CAPGO_KEEP_0__のセキュリティ慣行とを同期するチームでは、pushは同じレビューの範囲に含めるべきです。
push通知はユーザーフェイスのメッセージですが、auth、analytics、バックエンドイベントログと同じように取り扱う必要があります。
push通知はユーザーフェイスのメッセージですが、auth、analytics、バックエンドイベントログと同じように取り扱う必要があります。
| 通常は機能する | 通常は機能しない |
|---|---|
| 明確な値の説明後に許可を求める | 最初のフレームでポップアップする |
| 実機でテストする | シミュレータの登録を信頼する |
| デバイスのコンテキストとトークンを保存する | ユーザー レコードごとに 1 つのトークン |
| 小さなメタデータ ペイロードを送信する | 大きなまたは敏感な BLOB を埋め込む |
| フォアグラウンドとタップ イベントを別々に処理する | すべての通知が 1 つのパスを遷移することを前提とする |
| __CAPGO_KEEP_0__ | __CAPGO_KEEP_0__ |
__CAPGO_KEEP_0__
__CAPGO_KEEP_0__ Capgo __CAPGO_KEEP_0__