본문으로 바로가기
개발

딥링크·유니버설 링크 완전 정복 — 웹과 앱을 자연스럽게 잇기

A
AlwaysCorp 개발팀· 소프트웨어 엔지니어링 전문
||10분 읽기
#딥링크#UniversalLinks#AppLinks#iOS#Android#AASA#assetlinks#모바일#UX#마케팅

딥링크가 중요한 이유

마케팅 메일에서 "상품 보러가기" 링크를 눌렀더니 모바일 웹이 열리고, 다시 "앱에서 열기" 버튼을 눌러야 앱으로 넘어가는 경험을 해보셨을 겁니다. 이 한 번의 우회가 전환율을 30% 가까이 떨어뜨린다는 게 Branch.io의 2023년 리포트 결론입니다. 사용자는 의지가 식고, 광고비는 낭비됩니다.

올바르게 구현된 딥링크는 사용자가 클릭한 그 순간 앱이 즉시 정확한 화면으로 열립니다. 앱이 없으면 자연스럽게 웹으로 폴백하고, 앱을 새로 설치한 첫 실행 때도 원래 가려던 화면으로 직행시킬 수 있습니다(이걸 deferred deep link라고 부릅니다).

세 가지 방식 — 무엇을 골라야 하나

딥링크 라우팅 흐름 — Universal Links와 App Links의 동작

방식형식장점한계
URL Schememyapp://product/123구현 쉬움앱 미설치 시 에러, 충돌 가능
Universal Links (iOS)https://example.com/p/123HTTPS, 자연 폴백AASA 파일 + Apple 검증 필요
App Links (Android)https://example.com/p/123동일 원리assetlinks.json + autoVerify

2026년 운영 기준에서는 Universal Links + App Links 조합이 표준입니다. URL Scheme은 인앱 링크용 보조 수단으로만 남기는 게 좋습니다.

도메인의 https://example.com/.well-known/apple-app-site-association 경로에 다음 JSON을 두면 됩니다(MIME은 application/json, 리다이렉트 금지).

{
  "applinks": {
    "details": [{
      "appIDs": ["TEAMID.com.example.app"],
      "components": [
        { "/": "/products/*", "comment": "상품 페이지" },
        { "/": "/u/*",        "comment": "유저 프로필" },
        { "/": "/legal/*",    "exclude": true }
      ]
    }]
  }
}

Xcode 프로젝트에서는 Signing & Capabilities → Associated Domainsapplinks:example.com을 추가합니다. iOS는 앱 설치 시 이 파일을 한 번 가져와 검증하고 캐싱합니다. 검증 실패가 가장 흔한 디버깅 항목이니 swcutil dl이나 시뮬레이터의 xcrun simctl openurl 명령으로 확인합니다.

/.well-known/assetlinks.json을 같은 형식으로 둡니다.

[{
  "relation": ["delegate_permission/common.handle_all_urls"],
  "target": {
    "namespace": "android_app",
    "package_name": "com.example.app",
    "sha256_cert_fingerprints": ["AB:CD:..."]
  }
}]

AndroidManifest.xml의 인텐트 필터에 android:autoVerify="true"를 붙이면 OS가 설치 시 이 파일을 자동 검증합니다.

<intent-filter android:autoVerify="true">
  <action android:name="android.intent.action.VIEW"/>
  <category android:name="android.intent.category.DEFAULT"/>
  <category android:name="android.intent.category.BROWSABLE"/>
  <data android:scheme="https" android:host="example.com" android:pathPrefix="/products"/>
</intent-filter>

React Native·Flutter에서의 라우팅

OS가 앱을 띄우고 나면 라우터가 URL을 받아 화면을 띄워야 합니다. React Navigation v6에는 linking 옵션이 있습니다.

const linking = {
  prefixes: ["https://example.com", "myapp://"],
  config: {
    screens: {
      Product: "products/:id",
      Profile: "u/:username",
    },
  },
};
<NavigationContainer linking={linking}>...

Flutter는 go_routerredirect 콜백 또는 uni_links 패키지로 동일한 결과를 냅니다.

가장 까다로운 시나리오: 사용자가 링크를 클릭했는데 앱이 없어서 스토어로 갔고, 설치 후 처음 실행했을 때 원래 가려던 화면으로 데려가는 것.

순수 OS API만으로는 이 정보가 끊깁니다. 그래서 Branch·AppsFlyer·Firebase Dynamic Links 같은 SDK를 쓰거나, 직접 만든다면 다음 흐름을 씁니다.

  1. 링크에 ?fp=... 같은 핑거프린트(IP+UA+해상도)를 추가해 백엔드 임시 저장
  2. 앱이 처음 실행되면 같은 핑거프린트로 백엔드 조회
  3. 매칭되면 원래 URL로 라우팅

핑거프린트는 정확도가 70~90%이므로, 정밀해야 하면 클립보드 토큰이나 설치 후 첫 실행 시 사용자에게 "이전 화면으로 이동" 버튼을 보여주는 보조 UX를 곁들입니다.

다음 단계

딥링크는 한 번 잘 깔아두면 앱마케팅·이메일·SMS·QR코드·푸시 모두 같은 URL로 통합됩니다. 다음으로는 App Clips(iOS)·Instant Apps(Android) 로 설치 없이 앱 일부만 실행시키는 흐름, 그리고 푸시 알림 클릭 시 정확한 화면 라우팅까지 묶어두면 모바일 사용자 여정 전반이 하나의 URL 모델로 정리됩니다.

A

AlwaysCorp 개발팀

소프트웨어 엔지니어링 전문

얼웨이즈 블로그에서 유용한 정보와 인사이트를 공유합니다.