Use when adding push or local notifications to an Expo + RN app: requesting permissions at the right moment, registering for push (Expo push service in dev, APNs/FCM direct in prod), handling foreground + tapped + cold-start notifications, deep linking from a notification payload to a specific route, scheduling local notifications, badge management. Triggers on: "add push notifications", "local notification", "deep link from notification", "iOS/Android push setup", "register for notifications". Not for: backend setup that sends the notifications (rn-backend), animations on a notification badge (rn-animations-gestures).
설치
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
Use when adding push or local notifications to an Expo + RN app: requesting permissions at the right moment, registering for push (Expo push service in dev, APNs/FCM direct in prod), handling foreground + tapped + cold-start notifications, deep linking from a notification payload to a specific route, scheduling local notifications, badge management. Triggers on: "add push notifications", "local notification", "deep link from notification", "iOS/Android push setup", "register for notifications". Not for: backend setup that sends the notifications (rn-backend), animations on a notification badge (rn-animations-gestures).
rn-push-notifications — guardrail for push + local notifications
The 5 rules (non-negotiable)
expo-notifications is the SDK. Don't pull @react-native-firebase/messaging directly — Expo wraps APNs+FCM. Bare FCM is needed only for the rare case where you ship without Expo.
Request permission AT THE RIGHT MOMENT, not at app launch. Wait for a clear opt-in trigger (signup complete, settings screen, feature first use).
Setup setNotificationHandler at module level in app/_layout.tsx, ONCE. Not inside any component.
Handle the 3 entry paths: foreground (addNotificationReceivedListener), tapped while alive (addNotificationResponseReceivedListener), tapped on cold start (getLastNotificationResponseAsync).
Token storage: send the Expo push token to YOUR backend via authenticated HTTPS POST. Never store the raw token in AsyncStorage — use expo-secure-store or treat it as server-owned.
Quick decision tree
"Expo push service or direct APNs/FCM?" → references/decision-tree.md
"How do I wire up the handlers + deep linking?" → references/patterns.md
"What's the app.json / config plugin setup?" → references/setup.md
Common anti-patterns (NEVER do)
❌ Notifications.requestPermissionsAsync() in app/_layout.tsx on first render — bad UX (denied permanently if user says no by mistake).
❌ setNotificationHandler inside a screen component — runs on every mount, last one wins.
❌ Sending the Expo push token over plain HTTP — token = ability to spam the user.
❌ Reading notification.request.content.data without type-narrowing — runtime error on malformed payload.
❌ Forgetting getLastNotificationResponseAsync on cold start — deep link from tapped notification gets lost.
❌ Storing badge count in client state only — server is source of truth; client subtracts on read.