Powiadomienia Push w React Native 2026: Kompletny Przewodnik po Expo Notifications, FCM v1 i APNs
Konfiguracja powiadomień push w React Native 2026: expo-notifications, FCM v1 i APNs krok po kroku. Deep linking z Expo Router, obsługa tokenów, migracja z Legacy API oraz Notifee dla powiadomień lokalnych. Kod produkcyjny z sześciu wdrożeń.
Powiadomienia push w React Native w 2026 roku implementujesz najszybciej przez expo-notifications. Biblioteka opakowuje FCM v1 na Androidzie i APNs na iOS, generuje jeden token Expo Push Token i pozwala wysyłać powiadomienia jednym POST-em do exp.host/--/api/v2/push/send. Od czerwca 2024 stary FCM Legacy HTTP API jest wyłączony, a od Expo SDK 53 expo-notifications nie działa już w Expo Go, więc potrzebujesz development builda z EAS. Poniżej pokazuję konfigurację end-to-end, którą wdrożyłem w sześciu aplikacjach produkcyjnych, z realnym kodem, kosztami i pułapkami.
Expo Notifications w SDK 52+ wymaga development builda, bo Expo Go nie obsługuje już push notyfikacji na żadnej platformie.
FCM Legacy HTTP API został wyłączony 20 czerwca 2024; wszystkie integracje muszą używać FCM v1 z OAuth 2.0 i service account JSON.
Expo Push Service jest darmowy i bez limitu ilościowego, ale nie gwarantuje SLA. Dla misji krytycznych używaj FCM/APNs bezpośrednio.
Na Androidzie 13+ musisz jawnie prosić o uprawnienie POST_NOTIFICATIONS, a wszystkie powiadomienia wymagają skonfigurowanego kanału (Notification Channel).
Notifee to najlepszy wybór dla powiadomień lokalnych z zaawansowaną stylizacją (rich media, foreground service, chronografy), ale nie zastąpi backendu push.
Deep linking z powiadomień integruje się natywnie z Expo Router przez pole data.url, więc nie potrzebujesz osobnej biblioteki.
Jak działają powiadomienia push w React Native?
Powiadomienie push w React Native przechodzi przez trzy warstwy: twój backend → bramkę dostawcy (FCM lub APNs) → urządzenie użytkownika. Aplikacja rejestruje się w systemie operacyjnym, otrzymuje unikalny token urządzenia, przesyła go do twojego API, a serwer używa tego tokenu do wysyłania payloadów przez odpowiednią bramkę. Cały mechanizm jest asynchroniczny — twój serwer nigdy nie łączy się bezpośrednio z urządzeniem.
W ekosystemie Expo pojawia się czwarta warstwa: Expo Push Service. Zamiast integrować się z FCM i APNs osobno, wysyłasz jedno żądanie do serwerów Expo z tokenem w formacie ExponentPushToken[xxxxx], a Expo przekierowuje payload do właściwej bramki. To zdejmuje z ciebie 90% złożoności konfiguracji, kosztem zewnętrznej zależności. Dla większości aplikacji konsumenckich to wygrana. Ale dla fintechu lub healthcare, gdzie każde niedostarczone powiadomienie boli, wolę integrować się z FCM/APNs bezpośrednio.
Kluczowa zmiana w 2026: iOS i Android inaczej traktują powiadomienia w tle. iOS ma silent notifications (content-available: 1), które wybudzą aplikację na kilka sekund, ale system agresywnie throttluje ich dostarczanie. Android przez FCM data messages daje więcej swobody, ale od Androida 12 Doze mode i App Standby Buckets również ograniczają egzekucję. Nie projektuj krytycznej logiki wokół gwarantowanego dostarczenia w tle.
Konfiguracja Expo Notifications krok po kroku
Zaczynamy od nowego lub istniejącego projektu Expo z SDK 52 lub 53. Jeżeli używasz Expo Go, to od SDK 53 expo-notifications tam nie zadziała. Musisz zbudować development build przez EAS Build. To standardowy setup, który stosuję w każdym nowym projekcie:
npx expo install expo-notifications expo-device expo-constants
npx expo install --fix
eas build --profile development --platform all
W app.json (lub app.config.ts) dodaj plugin i skonfiguruj ikonę Androida oraz uprawnienia iOS:
Ikona Androida musi być białą, monochromatyczną PNG 96×96 z przezroczystym tłem. Inaczej system pokaże białą plamę. To najczęściej popełniany błąd, który widzę w code review.
Kod rejestrujący urządzenie i pobierający token trzymam w osobnym hooku, żeby był reużywalny między ekranami:
Zwrócony ExponentPushToken[...] wysyłasz na swój backend i zapisujesz w bazie razem z userId oraz platformą. Pamiętaj o rotacji: token może się zmienić po reinstalacji aplikacji lub czyszczeniu danych, więc odświeżaj go przy każdym starcie.
Migracja i konfiguracja FCM v1 API
Legacy FCM HTTP API (fcm.googleapis.com/fcm/send z Authorization: key=SERVER_KEY) został definitywnie wyłączony 20 czerwca 2024. Jeżeli twój backend wciąż go używa, powiadomienia po prostu przestały wychodzić. Wersja v1 wymaga OAuth 2.0 z tokenem Bearer generowanym z JSON-a service account.
W konsoli Firebase utwórz projekt, dodaj aplikację Android i pobierz google-services.json. Następnie w Project Settings → Service accounts kliknij Generate new private key. Dostaniesz JSON, który należy zabezpieczyć jak hasło. Ten plik idzie na backend, nie do repo aplikacji. W Expo od SDK 52 upload klucza FCM v1 robisz przez EAS CLI:
eas credentials
# wybierz Android → production → FCM V1 → Set up a Google Service Account Key
Po tej operacji Expo Push Service używa v1 do dostarczania na Android. Jeśli natomiast wysyłasz z własnego backendu bezpośrednio do FCM, potrzebujesz endpointu https://fcm.googleapis.com/v1/projects/{PROJECT_ID}/messages:send i tokenu Bearer. W Node.js najprościej użyć google-auth-library:
Google udostępnia szczegółowe wskazówki migracji do FCM v1 z mapowaniem pól ze starego API. Kluczowe różnice: priority ma teraz wartości HIGH/NORMAL zamiast 10/5, a payload dzieli się na sekcje notification, data, android, apns, webpush.
Konfiguracja APNs dla iOS
Na iOS masz dwie opcje autoryzacji przy APNs: klucz p8 (rekomendowany od 2020) lub certyfikat p12. Klucz p8 jest bezterminowy, jeden dla całego zespołu i obsługuje zarówno development, jak i production, dlatego jest domyślnym wyborem w EAS. Generujesz go w Apple Developer Portal → Certificates, Identifiers & Profiles → Keys, zaznaczając Apple Push Notifications service (APNs).
W EAS wysyłasz klucz analogicznie do FCM:
eas credentials
# iOS → production → Push Notifications: Manage your Apple Push Notifications Key
Jeśli integrujesz się z APNs bezpośrednio (bez Expo Push Service), potrzebujesz HTTP/2 z ALPN i JWT podpisanego kluczem p8 algorytmem ES256. Biblioteka @parse/node-apn zdejmuje z tego większość ceremonii:
Jeżeli korzystasz z Expo Push Service, całą warstwę FCM/APNs zamykasz w jednym wywołaniu HTTP. Endpoint https://exp.host/--/api/v2/push/send przyjmuje tablicę do 100 wiadomości na batch i zwraca ticket z ID, który potem wymieniasz na receipt. To dwustopniowa procedura, którą wielu deweloperów pomija — a bez sprawdzania receiptów nie wykryjesz, że token wygasł.
Prosty helper w Node.js z użyciem oficjalnego expo-server-sdk:
import { Expo, ExpoPushMessage } from "expo-server-sdk";
const expo = new Expo({ useFcmV1: true });
export async function sendPush(tokens: string[], title: string, body: string, data: object) {
const messages: ExpoPushMessage[] = tokens
.filter(Expo.isExpoPushToken)
.map((to) => ({
to,
sound: "default",
title,
body,
data,
priority: "high",
channelId: "default",
}));
const chunks = expo.chunkPushNotifications(messages);
const tickets = [];
for (const chunk of chunks) {
tickets.push(...(await expo.sendPushNotificationsAsync(chunk)));
}
// Zapisz ticket IDs do bazy, żeby po ~15 min pobrać receipts
const receiptIds = tickets
.filter((t) => t.status === "ok")
.map((t) => (t as { id: string }).id);
return { tickets, receiptIds };
}
Po 15–30 minutach pobierz receipts przez expo.getPushNotificationReceiptsAsync(). Receipt z DeviceNotRegistered oznacza, że użytkownik odinstalował aplikację lub token wygasł. Wtedy oznacz token jako martwy w bazie i przestań go używać. Bez tego kroku wysyłasz w kosmos, a metryki open-rate wyglądają jak katastrofa.
Deep linking i nawigacja po kliknięciu
Po kliknięciu powiadomienia chcesz zwykle zabrać użytkownika do konkretnego ekranu (do konwersacji, do zamówienia, do promocji). W React Native robisz to przez pole data.url w payloadzie i integrację z routerem. Jeżeli używasz Expo Router z nawigacją opartą na plikach, wystarczy że w handlerze zawołasz router.push(url):
import { useRouter } from "expo-router";
import * as Notifications from "expo-notifications";
import { useEffect } from "react";
export function useNotificationRouting() {
const router = useRouter();
useEffect(() => {
// 1. Kliknięcie w aplikacji uruchomionej lub w tle
const sub = Notifications.addNotificationResponseReceivedListener((r) => {
const url = r.notification.request.content.data?.url as string | undefined;
if (url) router.push(url);
});
// 2. Aplikacja uruchomiona z powiadomienia (cold start)
Notifications.getLastNotificationResponseAsync().then((r) => {
const url = r?.notification.request.content.data?.url as string | undefined;
if (url) router.push(url);
});
return () => sub.remove();
}, []);
}
Cold start to najczęstszy błąd. Listener addNotificationResponseReceivedListener nie zostanie odpalony, jeżeli aplikacja była całkowicie zabita. Tylko getLastNotificationResponseAsync zwróci ci wtedy payload. Bez tego drugiego kroku deep linking działa w 90% przypadków, a w 10% (najbardziej frustrujących dla użytkownika) po prostu otwiera pusty home screen.
Kiedy używać Notifee zamiast Expo Notifications?
Notifee to biblioteka od Invertase, która oferuje znacznie więcej niż expo-notifications w warstwie lokalnej (czyli powiadomień generowanych przez samą aplikację, nie przez serwer). Jeśli potrzebujesz chronografów, progress barów, foreground service na Androidzie, custom sound per kanał, obrazków rozszerzonych czy powiadomień z akcjami inline, Notifee jest jedyną sensowną opcją w React Native 2026.
Notifee nie zastępuje Expo Notifications w warstwie push. Nadal potrzebujesz FCM/APNs do dostarczania. Popularna hybryda wygląda tak: expo-notifications lub firebase-messaging odbiera push, a handler w twoim kodzie wywołuje notifee.displayNotification() z bogatszym UI. Tak zbudowaliśmy powiadomienia dostawcze w jednej z aplikacji e-commerce. Expo obsługuje transport, a Notifee renderuje kartę z mapą i przyciskiem "śledź kuriera".
Ograniczenia Notifee: nie działa w Expo Go (podobnie jak expo-notifications od SDK 53), wymaga skonfigurowania kanałów Androida ręcznie i ma płatną wersję Pro dla niektórych zaawansowanych funkcji na Androidzie (chronografy, decorated custom views). Dla większości projektów darmowa wersja pokrywa 100% potrzeb.
Debugowanie powiadomień push w produkcji
Debugowanie push notyfikacji jest specyficzne, bo błędy widzisz dopiero po dostarczeniu — a często nie widzisz ich wcale. Mój standardowy zestaw narzędzi:
Expo Push Notifications Tool. expo.dev/notifications pozwala wysłać testowy payload do konkretnego ExponentPushToken bez pisania backendu. Świetne do sanity checku na dev buildzie.
Firebase Console → Cloud Messaging → Send test message. Wysyła bezpośrednio przez FCM v1, omija Expo. Jeżeli tu działa, a przez Expo nie, to problem leży po stronie tokenu lub konfiguracji Expo credentials.
Konsola Xcode + PushKit logs na iOS. Pokazuje dokładny powód odrzucenia payloadu przez APNs (Invalid Topic, BadDeviceToken).
React Native DevTools, do inspekcji handlerów w JS i sprawdzenia, że listenery są prawidłowo zarejestrowane.
Receipty Expo. To jedyne źródło prawdy o dostarczeniu. Bez ich sprawdzania zgadujesz.
Najczęstsze błędy, na które trafiłem w produkcji: brak Notification Channel na Androidzie 8+ (powiadomienie znika bez śladu), błędny bundleId jako topic w APNs, powiadomienia w trybie Focus/Do Not Disturb na iOS 15+ (należy używać interruptionLevel: "timeSensitive" dla ważnych alertów) i wygasłe klucze APNs p12 (dlatego używam p8). W kontekście releasów mobile'owych zalecam też integrację z EAS Update dla aktualizacji OTA. Jeśli wypuszczasz nowe kanały push, możesz dostarczyć klienta bez czekania na review App Store.
Najczęściej zadawane pytania
Dlaczego Expo Notifications nie działa w Expo Go?
Od SDK 53 (kwiecień 2025) Expo usunęło wsparcie dla push notyfikacji w Expo Go na wszystkich platformach. Powodem są zaostrzone wymagania sklepów co do konfiguracji identyfikatorów aplikacji i credentiali. Rozwiązaniem jest development build przez eas build --profile development, który zachowuje szybki hot reload z pełną obsługą push.
Ile kosztuje Expo Push Service?
Expo Push Service jest darmowy bez limitu ilościowego, także w planie Free Expo Application Services. Nie ma jednak formalnego SLA na dostarczenie, więc dla aplikacji, gdzie każde niedostarczone powiadomienie kosztuje pieniądze (fintech, healthcare, delivery), lepiej integrować się z FCM v1 i APNs bezpośrednio.
Czy warto używać Notifee zamiast Expo Notifications?
Notifee używaj wtedy, gdy potrzebujesz zaawansowanych powiadomień lokalnych: chronografów, progress barów, foreground service, bogatych obrazków. Do samego transportu push (FCM/APNs) i tak potrzebujesz Expo Notifications lub react-native-firebase, bo Notifee ich nie zastępuje. Najczęściej używa się obu bibliotek razem.
Jak obsłużyć powiadomienie po kliknięciu przy zabitej aplikacji?
Użyj Notifications.getLastNotificationResponseAsync() na starcie aplikacji. addNotificationResponseReceivedListener odpala się tylko dla aplikacji uruchomionej lub w tle. Bez tego drugiego wywołania deep linking po cold starcie nie działa i użytkownik ląduje na home screenie zamiast na docelowym ekranie.
Czy FCM Legacy HTTP API nadal działa w 2026?
Nie. Google definitywnie wyłączył Legacy FCM HTTP API 20 czerwca 2024. Wszystkie integracje muszą używać FCM v1 z OAuth 2.0 i tokenem Bearer generowanym z JSON-a service account. Jeżeli twoje powiadomienia przestały wychodzić w połowie 2024, to najprawdopodobniejsza przyczyna.
Jak testować powiadomienia push na symulatorze iOS?
Od Xcode 11.4 symulator iOS obsługuje push notyfikacje. Możesz wysłać testowy payload przez przeciąganie pliku APNS JSON do okna symulatora lub komendę xcrun simctl push <device> <bundleId> payload.json. Symulator nie obsługuje jednak pełnej rejestracji APNs, więc do testów end-to-end potrzebujesz fizycznego urządzenia.
Praktyczny przewodnik po Maestro E2E dla React Native w 2026: instalacja w Expo, pierwszy scenariusz YAML, Maestro Studio, Maestro Cloud oraz integracja z EAS Build i GitHub Actions.
Nitro Modules to type-safe framework JSI dla React Native. Poznaj Nitrogen, model HybridObject, konfigurację Expo oraz benchmarki wydajności vs Turbo Modules.
Kompletny przewodnik po skracaniu cold startu w React Native 2026. Pomiar TTI w Instruments i Perfetto, Hermes V1, Nowa Architektura z Bridgeless Mode oraz cięcie bundla poniżej 4 MB, z konkretnymi patchami z produkcji.