React Native에서 Deep Link 처리 시 앱 상태에 따른 분기 처리

문제 상황

푸시 알림을 통해 특정 화면으로 이동하는 기능을 구현하던 중, 앱의 상태에 따라 Deep Link가 제대로 동작하지 않는 경우가 발생했다. 특히 앱이 완전히 종료된 상태에서 알림을 탭했을 때 초기 라우팅이 꼬이는 문제가 있었다.

앱 상태별 동작 차이

  • Foreground: Linking.addEventListener로 즉시 처리 가능
  • Background: 앱이 활성화되면서 이벤트 발생
  • Killed: Linking.getInitialURL()로 초기 URL 확인 필요

해결 방법

import { Linking, AppState } from 'react-native';

class DeepLinkHandler {
  constructor(navigation) {
    this.navigation = navigation;
    this.hasHandledInitialURL = false;
  }

  async initialize() {
    // 앱이 종료된 상태에서 실행된 경우
    const initialUrl = await Linking.getInitialURL();
    if (initialUrl) {
      this.handleURL(initialUrl);
      this.hasHandledInitialURL = true;
    }

    // 이후 Deep Link 처리
    Linking.addEventListener('url', this.handleDeepLink);
  }

  handleDeepLink = (event) => {
    if (this.hasHandledInitialURL) {
      this.hasHandledInitialURL = false;
      return;
    }
    this.handleURL(event.url);
  }

  handleURL(url) {
    const route = this.parseURL(url);
    if (route) {
      this.navigation.navigate(route.screen, route.params);
    }
  }

  parseURL(url) {
    // myapp://order/123 -> { screen: 'Order', params: { id: '123' } }
    const match = url.match(/\/\/(\w+)\/(\w+)/);
    if (!match) return null;
    return {
      screen: match[1],
      params: { id: match[2] }
    };
  }
}

주의사항

getInitialURL()addEventListener가 동시에 발동되는 경우가 있어, hasHandledInitialURL 플래그로 중복 처리를 방지했다. iOS와 Android에서 미묘하게 동작이 달라서 각 플랫폼별로 충분한 테스트가 필요했다.

재택 근무 중이라 실제 디바이스 테스트가 제한적이었는데, 시뮬레이터만으로는 찾기 어려운 엣지 케이스들이 있었다. 결국 개인 폰으로 TestFlight 빌드를 받아서 확인했다.