Vous êtes probablement à un point où l'application fonctionne, les utilisateurs sont connectés, et le produit souhaite maintenant des flux de réengagement qui ressentent naturellement. Les rappels de panier. Les invitations de révision. Les alertes de nouveaux messages. Les annonces de mise à jour. Le premier réflexe est souvent de « justifier la mise en place de la notification push », puis une semaine plus tard, vous êtes en train de déboguer pourquoi un appareil reçoit des alertes, le simulateur semble s'enregistrer correctement, et personne ne peut expliquer pourquoi les appuis ne lancent pas l'écran approprié.
C'est là que les notifications push Expo sont soit agréablement simples, soit surprenamment fragiles.
Expo offre aux équipes React Native une couche pratique sur APNs et FCM, ce qui est exactement pourquoi tant d'équipes l'utilisent. Mais la différence entre une démo et une mise en production prête à l'emploi est réelle. Le cycle de vie des jetons, le timing des autorisations, la mise en place des écouteurs, la conception des payloads, et la nettoyage du backend comptent tous. Si vous êtes également en train de livrer des changements logiques fréquents dans l'application, le besoin de discipline opérationnelle devient encore plus aigu, surtout si votre travail de rétention dépend de la messagerie fiable et de la vitesse de mise à jour. C'est la même préoccupation plus large derrière le travail de rétention des utilisateurs de l'application mobile: la livraison n'est utile que si l'expérience utilisateur autour d'elle est prévisible.
Table des matières
- La Fondation pour Rendre les Utilisateurs Engagés avec les Notifications Push d'Expo
- Ce que Expo abstrait réellement
- Demander la permission et capturer les jetons de notification
- Envoyer des notifications à partir de votre serveur
- Gérer les notifications entrantes dans votre application
- Meilleures pratiques de production et pièges courants
La Fondation pour engager les utilisateurs avec les notifications push Expo
Un Notification push Expo Une configuration est attrayante pour une raison au-dessus de toutes les autres. Elle supprime une grande partie de la complexité de la messagerie native que les équipes ne veulent pas gérer dès le premier jour. Au lieu de construire directement les canaux APNs et FCM, vous pouvez travailler avec la passerelle d'Expo et vous concentrer sur le comportement du produit, la routage, l'UX des permissions et la logique de message backend.
Cet abstrait n'empêche pas la notification push d'être "réelle". Il change simplement la direction de vos efforts en ingénierie.
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 42 milliseconde de temps de réponse médian, 273 milliseconde de latence p99et une taux journalier d'erreurs de 0,17% across des dizaines de millions de messages quotidiensl'analyse de benchmark de Knock sur l'envoi de notifications push d'Expo Knock’s Expo push API benchmark analysisCela devrait rassurer tout équipe se demandant si Expo est réservé uniquement aux prototypes.
Qu'est-ce que Expo abstrait réellement
Lorsque les équipes disent « notification push Expo », elles ont souvent plusieurs préoccupations distinctes regroupées ensemble.
- Routage du fournisseur : Expo transmet les messages à APNs pour iOS et FCM pour Android.
- Format du jeton : Votre serveur stocke et envoie un jeton de notification Expo au lieu de gérer la gestion de jetons spécifiques aux plateformes en premier.
- Contrat de demande : Vous envoyez un payload par la méthode POST vers Expo’s push API plutôt que d'intégrer directement les APIs de fournisseur natif.
Cela est utile, mais cela crée également une confusion courante. Les équipes supposent parfois que Expo est responsable de tous les problèmes de livraison. En pratique, de nombreux échecs proviennent de l'application code, de jetons obsolètes, de payloads malformés ou d'une mauvaise gestion des autorisations.
Règle pratique : Traitez Expo comme un couche de transport fiable, et non comme un substitut à une conception client et serveur solide.
Ce que signifie vraiment la maturité de production
Une démo fonctionnelle prouve uniquement que l'un des appareils a accepté un lot une seule fois. La maturité de production signifie autre chose :
| Préoccupation | Mentality de démo | Mentality de production |
|---|---|---|
| Autorisations | Demandez dès maintenant | Ask in context, after user value is clear |
| Jetons | Save once | Rafraîchir, dédupliquer, expirer et réconcilier |
| Payloads | Mettez tout dans data |
Conservez les payloads petits et axés sur l'action |
| Comportement de l'application | Afficher un avertissement | Se comporter correctement et gérer l'état en avant-plan |
| Opérations | Tests manuels | Réceptions, nettoyage, journaux et gestion d'incidents |
C'est la différence entre « notifications envoyées » et « notifications supportent un flux de workflow de produit réel. »
Initialiser la configuration du projet
Un grand nombre de douleurs d'envoi de notifications Expo commencent avant la première invitation de permission. Si votre configuration de projet est négligée, le client code peut paraître correct tout en faisant que l'application se comporte de manière incohérente entre les builds.

Start with the right libraries installed and a development environment that matches your build path. If you’re working beyond Expo Go, it helps to align your local workflow with a custom Expo development client setupPuisque le comportement des notifications doit souvent être validé dans une build qui ressemble plus à la production qu'à une rapide exécution de sandbox.
Au minimum, vous aurez besoin de:
Au minimum, vous aurez généralement besoin :
expo-notificationspour les demandes d'autorisation, la récupération de jetons, les écouteurs et la présentation des notifications.expo-devicecar vous devriez protéger la récupération de jeton avecDevice.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.
Gardez votre config explicite. Un minimum
Conservez votre config explicite. Un minimal app.json or app.config.js Les notifications doivent refléter le fait qu'elles font partie de votre contrat d'application, et non une pensée secondaire.
{
"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"
}
}
}
}
Un ou deux détails comptent ici :
- Identifiants de bundle et noms de packages need to match the app you actually ship.
- Le plugin de notifications s'assure que le projet natif obtient la configuration requise lors de la construction.
- L'ID du projet EAS a de l'importance lorsque votre récupération de jeton s'attend à ce que l'application soit associée au projet Expo correct.
Configurez un gestionnaire de notification dès le début
Attendez-vous trop longtemps pour définir le comportement des notifications. N'y allez pas. Placez-le près du démarrage de l'application afin que le comportement en avant-plan soit prévisible.
import * as Notifications from 'expo-notifications';
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldShowAlert: true,
shouldPlaySound: false,
shouldSetBadge: true,
}),
});
Les équipes décident si les notifications en arrière-plan doivent afficher une alerte, jouer un son ou affecter les badges. Le comportement exact dépend de votre produit. Une application de messagerie et une application de paiement ne feront pas les mêmes choix.
Si vous ne définissez pas intentionnellement le comportement en arrière-plan, votre équipe finira par déboguer les « notifications manquantes » qui ont été effectivement reçues mais n'ont jamais été présentées de la manière attendue par le produit.
Android nécessite une configuration de canal
Les canaux de notification Android ne sont pas facultatifs en pratique. Si vous les ignorez, vos alertes peuvent paraître incohérentes ou ne pas correspondre aux attentes des utilisateurs.
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,
});
}
Configurez cela lors de l'initialisation de l'application. Ensuite, maintenez les identifiants de canal stables. Les modifier fréquemment rend le comportement des notifications plus difficile à raisonner.
Demande de permission et capture de jetons de push
C'est là où de nombreuses équipes copient un snippet, puis finissent par regretter.
Les demandes de permission nécessitent un timing, une prise en compte de la plateforme et un traitement asynchrone discipliné. La capture de jetons doit se produire uniquement sur un appareil physique, uniquement après la résolution des permissions, et uniquement si vous êtes prêt à stocker le résultat sur votre serveur backend immédiatement.

La fonction client qui devrait être votre point de départ
Utilisez une fonction comme celle-ci comme point de départ
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 };
}
L'ordre compte. Vous vérifiez le type d'appareil en premier, configurez le comportement du canal Android, résolvez les permissions, validez la configuration du projet, puis demandez le jeton Expo.
Pourquoi Device.isDevice n'est pas facultatif
C'est l'une des rares erreurs qui génère beaucoup de bruit tout en paraissant innocente. Les équipes d'experts ne demandent la permission que conditionnellement lorsque Device.isDevice est vrai, et une faute courante est de passer sous silence ce garde-fou, ce qui pousse les développeurs à envoyer des notifications à des jetons de simulateur invalides et à blâmer Expo lorsque le problème est vraiment la configuration de l'application, comme décrit dans les notes d'implémentation de notifications Expo d'Eagerworks.
C'est pourquoi le contrôle se trouve en haut de la fonction. Ne le cachez pas derrière un helper. Faites-l’évident.
Les résultats de simulateur sont utiles pour les tests de l'interface utilisateur. Ils ne sont pas fiables pour valider l'enregistrement du jeton de notification.
Demandez la permission au bon moment
N'interrogez pas sur l'écran de démarrage. N'interrogez pas avant que l'utilisateur comprenne la valeur. Le meilleur moment est généralement après une action de l'utilisateur qui rend les avantages des notifications concrètes, comme l'activation des mises à jour de livraison, la participation à une conversation ou la sauvegarde d'un élément surveillé.
Une bonne implémentation suit généralement ce flux :
- L'utilisateur atteint une limite fonctionnelle significative.
- App explains the value of notifications in your own UI.
- L'application demande la permission système.
- App stores the token on the backend immediately if permission is granted.
Cette dernière étape est là où de nombreuses applications échouent. Elles récupèrent le jeton, le logent localement et reportent l'enregistrement backend. Plus tard, le support ne peut pas déterminer quel appareil avait quel jeton à quel moment.
Voici un exemple simple de stockage du jeton après l'enregistrement :
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;
}
Plus tard dans le flux de travail, ce guide est une référence visuelle utile :
For teams building release-heavy apps, it also helps to think of token registration as part of the app’s operational state, not just part of onboarding. That mindset fits well with broader Flux de travail de livraison d'applications Expooù le comportement de l'application peut changer fréquemment et où l'état du serveur doit rester synchronisé.
Envoi de Notifications à Partir de Votre Serveur
Once your backend has a valid Expo Push Token, sending a notification is straightforward. The hard part isn’t the request itself. It’s deciding what belongs in the payload and how much trust you place in client state.
Ce que chaque champ de payload doit faire 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;
}
Quels sont les champs du payload à utiliser
N'employez pas le payload comme un simple dépotoir. Assurez-vous que chaque champ ait un sens.
| Champ | Objectif | Conseils pratiques |
|---|---|---|
to |
Ciblez le jeton de notification Expo | Vérifiez qu'il appartient au dossier actuel du dispositif |
title |
Titre de la notification | Gardez-le court et lisible |
body |
Texte principal visible | Faites clair l'action |
sound |
Comportement du son du système | Use sparingly for high-value alerts |
data |
Meta-données spécifiques à l'application | Préférez les IDs et les indices de route aux contenus riches. |
Le data l'objet est où les flux de travail des produits deviennent utiles. Vous pouvez passer un type et un ID de document, puis laisser l'application récupérer les données les plus récentes lorsque l'utilisateur clique. C'est plus sûr que d'insérer des gros ou des blobs sensibles directement dans le payload.
Considérez les payloads comme étant petites et ennuyeuses
According to le guide de Courier sur les notifications ExpoLes jetons d'Expo Push devraient être considérés comme éphémères, les payloads qui dépassent les limites de taille d'environ 4 Ko peuvent être supprimés, et un modèle fiable est d'envoyer des payloads de métadonnées petites telles que { "type": "new_review", "id": 123 } au lieu de gros JSON ou de médias intégrés. Ce conseil correspond à ce qui fonctionne dans les systèmes réels. Les payloads petites échouent moins souvent et vieillissent mieux lorsque les logiques de l'application changent.
Send enough data to route the user. Fetch the rest after the app opens.
Les habitudes côté serveur utiles
A une fonction d'envoi de base est suffisante pour les tests. Une production ajoute généralement quelques responsabilités supplémentaires:
- Enregistrer les tentatives d'envoi : Save the notification intent with user ID, token, payload type, and timestamp.
- Separez la génération du contenu de la transmission : Construire le texte du message dans une couche et la demande d'Expo API dans une autre.
- Gérer les retours d'informations d'invalidation : Si Expo signale ultérieurement
DeviceNotRegisteredArrêtez la tentative et marquez ce jeton obsolète. - Utiliser un design compatible avec les webhooks : Si votre système émet déjà des événements, dirigez les déclencheurs de notification à travers le même type de Enregistrer les erreurs d'envoi : utilisez-les ailleurs.
Avant de déboguer les écouteurs du client, envoyez d'abord un test de push manuel. Si un jeton reçoit une notification plane avec un petit payload, votre chemin de transport est probablement en bonne santé. Si ce n'est pas le cas, ne commencez pas par modifier la navigation code. Commencez par valider le jeton, la forme du payload et l'état de permission.
Manipulation des Notifications d'Arrivée dans Votre Application
La livraison n'est qu'une moitié de la fonctionnalité. L'application doit faire quelque chose de cohérent lorsque la notification arrive et lorsque l'utilisateur la clique.
Cela signifie gérer deux moments séparés :
- la notification arrive tandis que l'application est ouverte
- l'utilisateur interagit avec la notification depuis la zone de notification ou l'écran de verrouillage

Événements de réception de fond et de réponse de l'utilisateur sont différents
Un setup fiable comprend généralement les deux écouteurs :
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 exécuté lorsque l'application est active. addNotificationResponseReceivedListener exécuté lorsque l'utilisateur clique sur une notification livrée. N'y combinez pas mentalement. Ils servent des chemins UX différents.
Lisez le payload de données et naviguez intentionnellement
Voici un modèle pratique pour la gestion des clics :
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');
}
Ce modèle reste résilient car le payload contient des indices de routage, pas des documents complets. Si l'ordre a changé depuis que la notification a été envoyée, l'application peut récupérer l'état serveur actuel après la navigation.
Une notification cliquée devrait conduire à un destin évident. Si votre chemin de secours est vague, les utilisateurs le remarquent immédiatement.
Foreground behavior should match user context
When the app is already open, blindly showing a system-style alert can feel clumsy. Sometimes the right move is an in-app banner, badge update, or silent refresh. A support inbox screen might not need a visible alert when the user is already reading that conversation.
Votre écouteur de premier plan doit donc se diviser en fonction de la route et du type de notification. Par exemple :
- Écran de chat ouvert : ajoutez le message et évitez un bandeau redondant
- Dashboard ouvert: affichez un toast léger dans l'application
- Événement critique de compte : faites apparaître un traitement UI plus fort
A une approche simple, cela ressemble à ceci :
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
}
Si votre application ne distingue pas ces contextes, les utilisateurs ressentiront une fatigue des notifications plus rapidement, même si la livraison est correcte techniquement.
Meilleures Pratiques et Pièges de Production
Most broken Expo push notification setups don’t fail because Expo is too limited. They fail because teams assume the token is permanent, payloads can carry anything, and app updates won’t affect notification logic.
Cette supposition ne survit pas en production.

Les jetons sont éphémères, pas des enregistrements d'identité
Un jeton de notification Expo est mieux traité comme un bail, et non comme un identifiant de dispositif à vie. Les jetons peuvent tourner après réinstallation, changement d'OS ou autres événements de cycle de vie. Si un jeton revient finalement DeviceNotRegisteredVotre serveur backend devrait cesser de le traiter comme actif.
Un modèle de serveur backend pratique stocke :
- user ID
- Plateforme
- installer les métadonnées scoping
- token actuel
- timestamp de dernière vue
- statut comme actif, obsolète ou révoqué
Ne stockez pas un champ de jeton sur la table utilisateur et croyez que c'est terminé. Les utilisateurs ont plusieurs appareils, et les appareils changent d'état.
Refresh strategy matters more than most tutorials admit
La documentation officielle de l'écosystème laisse un véritable écart opérationnel ici. Le contenu de notification push Expo existant n'explique souvent pas comment maintenir la validité du jeton. Cycles de révision de l'App Store et mises à jour OTACela affecte la façon dont vous conçez les déclencheurs de mise à jour. Les bonnes occasions pour réconcilier l'état du jeton incluent : Documentation des notifications Expo.
L'actualisation de l'application est une bonne occasion de réconcilier l'état du jeton
- Lancement de l'application après une mise à jour
- Connexion de l'utilisateur
- Modification des paramètres d'autorisation
- La rotation des clés sur votre processus de publication
- Flux de récupération après les tickets de support liés aux notifications
La sécurité et la conformité ne doivent pas être traitées en fin de sprint
Beaucoup de tutoriels Expo se concentrent sur les mécanismes et ignorent le risque opérationnel. C'est acceptable pour les applications de loisirs. Ce n'est pas acceptable pour les produits de commerce réglementé, adjacents à la santé ou au financement.
La discussion de Courier sur les lacunes de notification Expo axée sur les entreprises highlète un manque de conseils pratiques autour de la gestion du consentement, des traçages d'audit et de la minimisation de l'exposition des données sensibles. Le retour d'expérience technique est simple :
- Don’t put sensitive business data in notification text or payload metadata.
- Enregistrez les modifications de consentement côté serveur.
- Enregistrez les notifications envoyées à quelles jetons.
- Use IDs in payloads and fetch protected content after app open.
Pour les équipes alignant les opérations de mise à jour avec Conformité à l'App Store et pratiques de sécurité APIpush devrait être inclus dans le même cadre de revue que l'authentification, l'analytique et la journalisation d'événements backend.
Les notifications push sont des messages destinés aux utilisateurs, mais elles constituent également un problème de systèmes distribués. Traitez-les avec la même attention que vous accordez à l'état d'authentification et aux événements de paiement.
Ce qui fonctionne généralement et ce qui casse généralement
| Ce qui fonctionne généralement | Ce qui fonctionne généralement |
|---|---|
| Demander la permission après une explication claire de la valeur | Demander la permission à la première frame |
| Tester sur des appareils réels | Se fier à l'enregistrement du simulateur |
| Stocker les jetons avec le contexte de l'appareil | Un jeton par enregistrement d'utilisateur |
| Envoi de petites payloads de métadonnées | Intégration de gros ou de blobs sensibles |
| Gestionner les événements de fond et de tap séparément | Assuming all notifications follow one path |
| Expiration agressive des jetons obsolètes | Réessai des jetons morts à l'infini |
Une bonne configuration de notification Expo n'est pas compliquée. C'est une discipline.
If your team ships frequent app logic changes and needs tighter control over release behavior, rollbacks, and delivery visibility, Capgo vaut le coup d'œil. Elle aide les équipes mobiles à envoyer des mises à jour rapidement sans attendre la revue des magasins, ce qui est particulièrement utile lorsque les flux de notification, la logique de routage ou les corrections côté client doivent atteindre les utilisateurs rapidement.