앱 설치 후 광고 링크로 보내기 (Deferred Deeplink)


우리는 Next.js 기반 웹 서비스를 운영하고 있고, Capacitor를 사용해 웹뷰 기반 앱을 함께 제공하고 있다.

마케팅을 본격적으로 시작하면서 자연스럽게 다음 요구사항이 생겼다.
광고나 공유 링크를 눌러 들어온 유저가 앱이 설치되어 있지 않으면 스토어로 이동하고, 설치가 끝난 후에는 처음 눌렀던 바로 그 페이지로 이동해야 한다.

  • 이 개념을 Deferred Deep Link라고 한다.

Deferred Deep Link란 앱이 설치되어 있지 않은 상태에서 링크를 클릭했을 때, 스토어로 이동해 앱을 설치한 뒤에도 처음 클릭했던 링크의 목적지로 이동시켜주는 것을 의미한다.

예를 들어 이런 흐름이다.

  • 광고 링크: https://ourApp.onelink.me/…/chats
  • 앱 미설치 → App Store / Play Store 이동
  • 앱 설치 후 실행
  • 홈 화면이 아닌, 처음 설정되어 있던 링크인 /chats 로 이동

우리는 마케팅 유저에 대해 온보딩 과정을 거치도록 했다.
온보딩은 /onboarding 이라는 경로에서 시작되기 때문에, 앱 설치 후 바로 /onboarding으로 이동시켜야 했다.

Deferred Deep Link가 설정되어 있지 않으면, 유저는 온보딩 프로세스 자체를 거치지 못 하고 홈화면으로 떨어지게 된다.
이렇게 되면 온보딩 프로세스가 가지는 의미가 완전히 없어지고, 갑자기 홈화면으로 떨어지게 된 사용자는 당황하게 될 것이다.


딥링크 문제를 단순하게 생각하면 이렇게 보일 수 있다.

  • 딥링크로 들어오면 그 URL로 보내면 되지 않나?

앱이 이미 설치돼 있는 경우에는 그렇게 처리하면 되지만, 앱이 설치되어있지 않은 경우에는 스토어에서 설치 후 “앱 열기 / Open App”을 클릭했을 때 처음 클릭한 링크를 기본적으로 보존할 수 있는 방법이 없다.

그래서 아래 과정을 거쳐 앱 설치 후 클릭 경로로 보내게 된다.

1. 사용자가 광고를 클릭한다.

  • 광고를 클릭하면 AppsFlyer는 캠페인 정보, 딥링크 파라미터, 디바이스 정보 등을 서버에 기록한다.

2. 사용자가 앱을 설치한다.

  • 이 단계에서는 아직 앱과 연결되지 않은 상태
  • 단지 이 디바이스는 이런 캠페인으로 들어왔다는 기록만 있는 상태

3. 사용자가 앱을 실행한다.

  • 앱이 실행되면서 AppsFlyer가 초기화 된다.
  • 이 앱 실행이 이전에 기록된 클릭과 같은 디바이스라고 판단되면 서버에 저장돼 있던 딥링크 정보를 내려줌
  • 그 딥링크 정보를 참조해서 클릭 경로로 보냄

구현

1. AppsFlyer 초기화

  • AppsFlyer는 네이티브 SDK이기 때문에 웹 환경에서는 초기화할 필요도, 의미도 없다.
TypeScript
if (!Capacitor.isNativePlatform()) return;
await initializeAppsFlyer()  

2. UDL(Unified Deep Linking) 콜백에서 딥링크 수신

  • AppsFlyer는 앱 실행 초기에 UDL_CALLBACK을 통해 설치 전 딥링크 정보를 전달해준다.
  • 이 시점에는 WebView가 준비되기 전일 수도 있기 때문에 바로 라우팅 하지 않고, 딥링크 정보를 안전하게 보존하기만 한다.
TypeScript
function setupDeepLinkListener(): void {
  AppsFlyer.addListener(
    AFConstants.UDL_CALLBACK,
    async (res) => {
      if (res.status === 'FOUND') {
        handleDeepLink(res.deepLink)
      }
    },
  )
}

3. 딥링크 정보 임시 저장

  • 딥링크 정보를 sessionStorage에 임시 저장해둔다.
TypeScript
window.sessionStorage.setItem(
  'appsflyer_campaign_context',
  JSON.stringify({
    deep_link_value,
    deep_link_sub1,
  }),
)

4. appUrlOpen 이벤트에서 최종 라우팅

  • 실제 라우팅은 Capacitor의 appUrlOpen 이벤트에서 처리했다.
  • 기존 Deep Link 처리와 충돌하면 안 되기 때문에, 딥링크 라우팅 관련 로직은 모두 Capacitor의 appUrlOpen 이벤트에서 처리
TypeScript
const campaign = getCampaignContext()

if (campaign?.deep_link_value) {
  router.replace(`/${campaign.deep_link_value}`)
  clearCampaignContext()
}

앱이 이미 설치된 상태에서 One Link를 클릭하거나, Deeplink를 타고 들어온다면 campaign이 빈 값일 것이므로 일반 딥링크 플로우로 처리된다.

앱이 설치 되지 않은 상태에서 One Link를 통해 들어온다면 campaign이 사전에 설정된 상태일 것이므로, campaign에 설정된 값으로 라우팅이 처리된다.


온보딩 플로우를 설계하고 구현하는 것만 해도 상당히 많은 작업량이었어서, Deferred Deep Link 입히는 건 Deep Link와 크게 다르지 않게 구현 할 수 있을 거라고 생각해서 문제를 조금 가볍게 생각했다.

하지만 실제로 구현을 시작해보니, 생각보다 앱 설치 전후의 흐름은 생각보다 훨씬 복잡했고, 딥링크를 언제, 어떤 타이밍에, 어떤 책임으로 처리하느냐를 종합적을 고려해야 하는 어려운 문제였다.

특히 웹뷰 기반 앱에서는 웹뷰 초기화 시점 때문에 링크를 받았다는 사실보다 그 링크를 언제 소비하느냐가 훨씬 중요했고, 무작정 라우팅을 시도하면 홈 화면으로 튕겨버리는 경우가 훨씬 많았다.

Deferred Deep Link는 광고로 들어온 사용자가 처음 경험하게 되는 기능으로 (사용자는 못 느끼겠지만), 구현이 안 되어있으면 전환율과 사용자 경험에 치명적인 영향을 줄 수도 있다.

마무리 하며, 우리가 사용하고 있는 앱은 모두 엄청난 디테일로 개발되었다는 사실을 다시 한 번 체감하는 순간이었다.

답글 남기기

Dalmeng's Footprints에서 더 알아보기

지금 구독하여 계속 읽고 전체 아카이브에 액세스하세요.

계속 읽기