Push Notifications & Deep Linking Architecture
📖 Concept
Push notifications and deep linking are architecturally interconnected — a notification tap often deep links to a specific screen. Getting them right at scale requires careful architecture.
Push Notification Architecture:
Server → APNs (iOS) / FCM (Android) → Device OS → App
↓
Foreground: onMessage handler
Background: onBackgroundMessage
Killed: onNotificationOpenedApp
The 3 notification scenarios:
- Foreground — App is open and active. You receive the payload and decide how to display it (in-app banner, badge, ignore).
- Background — App is in memory but not visible. System shows the notification. When tapped, your app receives the data.
- Killed/Quit — App is not running. System shows the notification. When tapped, app launches and receives the initial notification.
Deep Linking Architecture: Deep links navigate users to specific content within your app. Sources:
- Push notification taps
- External URLs (email, SMS, web)
- Universal Links (iOS) / App Links (Android)
- Other apps (social media shares)
Architecture for handling deep links:
Deep Link Received
→ URL Parser (extract route + params)
→ Auth Gate (is user logged in?)
→ If no: queue link → show login → after login, navigate
→ If yes: navigate directly
→ Navigation Handler
→ Stack reset if needed (clear back stack)
→ Navigate to target screen with params
💻 Code Example
1// === PUSH NOTIFICATION ARCHITECTURE ===23import messaging from '@react-native-firebase/messaging';4import notifee, { AndroidImportance } from '@notifee/react-native';56// 1. Notification Service — centralized handling7class NotificationService {8 private static instance: NotificationService;9 private deepLinkHandler: DeepLinkHandler;1011 static getInstance() {12 if (!this.instance) this.instance = new NotificationService();13 return this.instance;14 }1516 async initialize() {17 // Request permission18 const status = await messaging().requestPermission();19 if (status === messaging.AuthorizationStatus.AUTHORIZED) {20 const token = await messaging().getToken();21 await this.registerToken(token);22 }2324 // Token refresh25 messaging().onTokenRefresh(this.registerToken);2627 // Foreground messages28 messaging().onMessage(async (remoteMessage) => {29 await this.handleForegroundNotification(remoteMessage);30 });3132 // Background/quit notification tap33 messaging().onNotificationOpenedApp((remoteMessage) => {34 this.handleNotificationTap(remoteMessage);35 });3637 // App opened from killed state via notification38 const initialNotification = await messaging().getInitialNotification();39 if (initialNotification) {40 this.handleNotificationTap(initialNotification);41 }42 }4344 private async handleForegroundNotification(message: FirebaseMessagingTypes.RemoteMessage) {45 // Show in-app notification using Notifee46 await notifee.displayNotification({47 title: message.notification?.title,48 body: message.notification?.body,49 data: message.data,50 android: {51 channelId: 'default',52 importance: AndroidImportance.HIGH,53 pressAction: { id: 'default' },54 },55 });56 }5758 private handleNotificationTap(message: FirebaseMessagingTypes.RemoteMessage) {59 // Extract deep link from notification data60 const deepLink = message.data?.deepLink as string;61 if (deepLink) {62 this.deepLinkHandler.handle(deepLink);63 }64 }6566 private async registerToken(token: string) {67 await api.registerPushToken({ token, platform: Platform.OS });68 }69}7071// === DEEP LINKING ARCHITECTURE ===7273// 2. Deep Link Handler with auth gating74class DeepLinkHandler {75 private pendingLink: string | null = null;76 private navigationRef: NavigationContainerRef<any>;7778 constructor(navigationRef: NavigationContainerRef<any>) {79 this.navigationRef = navigationRef;80 }8182 async handle(url: string) {83 const parsed = this.parseDeepLink(url);84 if (!parsed) return;8586 // Auth gate87 const isAuthenticated = await authService.isAuthenticated();88 if (parsed.requiresAuth && !isAuthenticated) {89 this.pendingLink = url; // Queue for after login90 this.navigationRef.navigate('Login');91 return;92 }9394 // Navigate95 this.navigateTo(parsed);96 }9798 // Call this after successful login99 processPendingLink() {100 if (this.pendingLink) {101 const link = this.pendingLink;102 this.pendingLink = null;103 this.handle(link);104 }105 }106107 private parseDeepLink(url: string): ParsedLink | null {108 // myapp://product/123 → { screen: 'ProductDetail', params: { id: '123' } }109 // myapp://chat/456 → { screen: 'ChatRoom', params: { roomId: '456' } }110 const routes: Record<string, RouteConfig> = {111 'product/:id': { screen: 'ProductDetail', requiresAuth: false },112 'chat/:roomId': { screen: 'ChatRoom', requiresAuth: true },113 'profile/:userId': { screen: 'UserProfile', requiresAuth: true },114 'settings': { screen: 'Settings', requiresAuth: true },115 };116117 // Match URL against route patterns and extract params118 for (const [pattern, config] of Object.entries(routes)) {119 const match = matchPath(url, pattern);120 if (match) {121 return { ...config, params: match.params };122 }123 }124 return null;125 }126127 private navigateTo(link: ParsedLink) {128 // Reset stack and navigate — prevents deep back stacks129 this.navigationRef.dispatch(130 CommonActions.reset({131 index: 1,132 routes: [133 { name: 'Home' }, // Always have Home at the bottom134 { name: link.screen, params: link.params },135 ],136 })137 );138 }139}140141// 3. React Navigation deep linking config142const linking = {143 prefixes: ['myapp://', 'https://myapp.com'],144 config: {145 screens: {146 Home: '',147 ProductDetail: 'product/:id',148 ChatRoom: 'chat/:roomId',149 UserProfile: 'profile/:userId',150 Settings: 'settings',151 },152 },153};
🏋️ Practice Exercise
Push Notifications & Deep Linking Exercises:
- Implement a complete push notification setup with Firebase Cloud Messaging
- Handle all 3 notification states (foreground, background, killed) correctly
- Build a deep link handler with auth gating and pending link queue
- Create notification channels on Android with different priorities
- Implement rich notifications with images and action buttons
- Test deep linking from: push notifications, external URLs, and universal links
⚠️ Common Mistakes
Not handling the 'app killed' notification scenario — getInitialNotification() is often forgotten, losing the user's intent
Navigating before the navigation container is ready — use a ref and wait for isReady signal
Not gating authenticated deep links — navigating to a chat room before login causes crashes or empty screens
Hardcoding deep link routes instead of using a route map — makes changes error-prone and untestable
Not handling notification permissions gracefully — crashing or showing nothing when permission is denied
💼 Interview Questions
🎤 Mock Interview
Mock interview is powered by AI for Push Notifications & Deep Linking Architecture. Login to unlock this feature.