본문 바로가기
공부/TIL•기타

DeepLink

by svcbn 2026. 1. 10.

12/31

 

 

DeepLink

딥링크는 사용자를 모바일 앱 내의 특정 페이지나, 원하는 페이지로 직접 이동 시키는 기술이다. 특정 링크를 클릭했을 때나, 링크를 클릭하지 않았더라도 뭔가의 앱으로 이동하는 듯한 기능을 일상생활에서도 충분히 느껴본 적 있을 것이다.
예를 들어보자면, 인터넷을 돌아다니다 보면 흔하게 납치(?) 당하는 쿠팡이라던가, 많은 기능들로 바로 유도되는 토스라던가.. 일련의 동작으로 앱만 켜지는 것이 아닌, 특정한 상품 링크 혹은 기능으로 바로 이동되는 것을 자주 겪었을 것인데, 그게 바로 이 DeepLink 라고 할 수 있다.

현재 프로젝트 내의 기능으로도 넣어야 한다는 Tencent 측의 요청이 있어 작업을 시작했었고, 현재는 마무리했다.
구현 방법에 대해 일일히 설명하는 것도 나쁘진 않지만, 그것은 더 자세히 설명된 글들이 많기 때문에.. 패스하고 소개 정도로만 작성해 볼 예정. 딥링크라고 통합하여 이야기하지만, 두 가지 방식이 혼합되어 있다.

 

URL Scheme 방식

모바일 앱에 고유한 주소값을 부여하여, 일치하는 schema 가 발생하면 해당 앱의 동작으로 유도하는 방법. 고전적으로 가장 많이 쓰이던 방식이다. 각 앱의 설정파일에, AOS 의 경우 AndroidManifest.xml, IOS 의 경우 Info.plist 에 특정 Scheme 값을 등록해놓으면, 특정 링크가 이 앱의 것으로 판정되면서 해당 앱에 미리 등록된 동작들을 따르게 된다.

스킴(Scheme)://경로(Path)?파라미터(Query)

보통 이런 형태를 띄게 되며, Scheme 과 Path, Query 전부 커스텀해서 원하는 방식으로 사용할 수 있으나, 뒤에서 소개할 Universal Link 방식과의 로직상 호환을 위해 그쪽과 형태를 맞추는 것이 대세인 듯?

장점은, 구현 상 자유로움과, 아직도 많은 기기에서 바로 사용할 수 있는 호환성, 그리고 Universal Link 와는 다르게 따로 설정할 것이 없는 편리함도 있다.
장점이 있으면 단점이 있는 법... 앱이 미설치 된 경우, 기기에서 해당 커스텀 Scheme 을 식별할 수 없다. 그리고, 말 그대로 자유로운 커스텀 Scheme 을 사용할 수 있기 때문에, 다른 앱과 혹시나 겹치게 설정되었을 경우 어느 쪽으로 동작할지 보장할 수 없는 경우도 있고, 이를 통해 다른 앱에서 이런 Schema 동작을 가로챌 수도 있다.

이런 단점들을 보완하기 위해 고안된 방법이, 바로 다음으로 소개될 다른 방법인데,

 

 

Universal Link / App Link 방식

AOS 에서는 App LInk, IOS 에서는 Universal Link. 구분해서 부르기는 하지만, 사실상 동작은 완전히 같은 방식이다. 링크를 클릭하거나 동작하는 경우, 앱이 설치되어 있으면 URL Scheme 방식처럼 같은 동작을, 설치되어 있지 않다면 해당 앱의 설치 링크로 유도되도록 할 수 있다. 따로 설정을 거쳐야 하기는 하지만..
아래에서는 설명 편의상 Universal Link 하나만을 사용하도록 하겠다. 뭔가 네이밍이 더 직관적인 느낌이라..

https://경로(Path)?파라미터(Query)

그래서, 크게 형태가 다르지 않지만 굳이 비교하자면, 커스텀 할 수 있었던 기존방식의 Scheme 과는 달리 일반적인 링크인 https:// 의 형태를 띈다. 최근의 AOS, IOS 둘 다 http 방식은 지원하지 않으니 주의. 앞에서 뒤쪽의 Path 와 Query 또한 커스텀이 가능하나, 권장하지 않았던 이유도 바로 이것. 뒤쪽의 형태를 같도록 설계 해 놓으면, 약간의 설정만으로 두 가지 방식이 같은 동작을 하도록 유도할 수 있다.

그렇다면, 필연적으로 URL Scheme 방식과는 뭐가 다른지 알아볼 차례인데..
먼저 설정해 줘야 하는 것이 있다. 일반적으로 https:// 로 시작하는 링크들은, 웹의 링크인 경우가 많기 때문에 자연스럽게 브라우저로 연결된다. 그렇기 때문에, 이를 앱의 뭔가 동작으로 사용하기 위해서는, 검증 절차가 필요한데...

먼저 내가 소유중인 특정 도메인의 지정된 경로에 검증 파일을 추가해야 한다.
갑자기 무슨 소리인가 싶은데, 앱 내부 혹은 외부에서 들어온 링크가 내가 원하는 도메인인지를 먼저 확인하고, 이를 충족했을 때만 앱의 동작으로 유도하는 것이다. 검증 절차를 통과하지 못한다면 그냥 브라우저의 링크 연결로 넘어가는 식.
내가 소유한 도메인의 서버에, 지정된 경로에, 각각 지정된 파일이 있어야 하는데,

https://example.com/.well-known/assetlinks.json             // AOS
https://example.com/.well-known/apple-app-site-association  // IOS

사용할 도메인의 하위 .well-known/ 경로에 특정 파일들이 미리 업로드되어있어야 한다. AOS 의 경우에는 assetlinks.json 파일을, IOS 의 경우에는 apple-app-site-association 파일이 있어야 하는데, 해당 경로의 이 파일들을 확인하고, 앱은 이 링크가 DeepLink 방식으로 동작해도 되는 것인지, 아니면 그냥 브라우저로 처리할지 결정한다.

 

Assetlinks.json

[{
  "relation": ["delegate_permission/common.handle_all_urls"],
  "target": {
    "namespace": "android_app",
    "package_name": "com.example.myapp",
    "sha256_cert_fingerprints": [
     14:6D:E9:83:C5:73:06:50:D8:EE:B9:95:2F:34:FC:64:16:A0:83:42:E6:1D:BE:A8:8A:04:96:B2:3F:CF:44:E5
    ]
  }
}]

각각 항목을 설명해보면,

relation
어떤 권한을 줄지 명시한다. 몇 종류가 있는데..
delegate_permission/common.handle_all_urls : 이 웹사이트의 모든 URL 처리를 이 앱에서 위임하겠다는 뜻. 이 설정이 있어야 사용자가 링크를 클릭했을 때 브라우저 대신 앱이 즉시 실행된다.
delegate_permission/common.get_login_creds : 웹사이트와 앱이 로그인 정보를 공유할 때. 이 권한이 있으면 Chrome 브라우저 같은 곳에서 저장된 웹사이트 비밀번호를 앱에서 자동으로 불러오거나 할 수 있다.
dynamic_app_link_components : 내가 사용했던 방식. Android 15+ 버전에서 사용할 수 있으며, 특정 경로만 앱으로 열거나, 제외하는 등 동적 라우팅 규칙을 사용할 수 있다.

namespace
대상이 속한 플랫폼 유형을 식별한다.
android_app : 대상이 안드로이드 앱. 딥링크 구현 시에는 반드시 이쪽을 사용해야 한다. 이후 package_name 과 sha256 서명값을 통해 앱을 정확히 지목.
web : 딱히 여기서는 필요 없지만.. 설명하자면 대상이 웹사이트인 경우. 주로 앱에서 웹사이트의 권한을 확인하거나, 로그인 정보를 공유할 때 상호 인증용으로 사용.

package_name / sha256_cert_fingerprints
해당 앱마다 다른 값. 작업하려는 앱의 패키지명과, 스토어에 등록된 sha256 서명값을 넣으면 된다. 개발시에는 debug 버전과 release 버전의 서명값이 다르니 주의. 구분하기 귀찮다면, sha256~ key 의 value 값이 배열이니 두개 다 넣어버려도 된다.

 

apple-app-site-association

{
  "applinks": {
    "details": [
      {
        "appID": "ABCDE12345.com.example.myapp",
        "components": [
          {
            "/": "/products/*",
            "comment": "상품 상세 페이지로 연결"
          }
        ]
      }
    ]
  }
}

IOS 에서는 따로 서명같은 검증을 요구하지는 않는다. 대신, IOS 13+ 부터 파일 내부의 components 에서 어떤 경로는 어떻게 연결할지에 대한 설정을 따로 해줄 수 있는데, 자세한 기능들은 아래서 알아보도록 하고...
주의할 것은, 형태는 JSON 과 같은 형태지만, 확장자가 없는 파일이다. 없는게 정상이니 당황하지 말자.

 

{
  "applinks": {
    "details": [
      {
        "appID": "TEAMID1234.com.example.myapp",
        "components": [
          {
            "/": "/help/login",
            "exclude": true,
            "comment": "로그인 도움말 페이지는 앱이 아닌 웹브라우저에서 열리도록 제외"
          },
          {
            "/": "/settings/*",
            "exclude": true,
            "comment": "설정 관련 모든 경로는 웹에서만 유지"
          },
          {
            "/": "/products/p_????",
            "comment": "p_ 뒤에 4글자 ID가 붙는 상품 상세페이지만 앱으로 연결 (예: /products/p_1234)"
          },
          {
            "/": "/items/*",
            "comment": "items 하위의 모든 경로는 앱으로 연결"
          },
          {
            "/": "/search",
            "query": { "q": "*", "category": "books" },
            "comment": "검색어(q)가 있고 카테고리가 books인 검색 결과만 앱으로 연결"
          },
          {
            "/": "*",
            "comment": "위의 조건에 해당하지 않는 나머지 모든 경로는 기본적으로 앱으로 연결"
          }
        ]
      }
    ]
  }
}

* : 모든 문자와 매칭. 다시 말해, 해당 경로 하위는 모두 해당한다.
? : 정확히 1개의 문자와 매칭. 예제에서는 ???? 4개를 사용하여, ID가 4개인 페이지만 일치하도록 사용했다.
exclude: true : 이 항목이 true 면, 해당 경로들은 앱을 실행하지 않고 무조건 브라우저에서 처리한다. 특정 기능이 웹에서만 동작해야 할 때 필수.
query : 이 항목으로는, URL 뒤의 파라미터 조건까지 체크하여 앱 연결 여부를 결정할 수 있다.

주의사항으로는,
배열상 먼저, 그러니까 상단에 위치한 규칙이 우선시된다는 것. 그래서 특정 경로 제외(exclude) 같은 것을 작성하려면 모든 경로 허용(/*) 보다 먼저 작성해야 한다.
또한, 대소문자가 구분되며, 128KB 의 크기 제한이 있다.

 

이런 설정들을 거치고 나면, 드디어 앱에서 Universal Link 를 처리할 수 있게 된다. 이후로는 앱으로 들어온 동작을 감지해서, 웹페이지를 띄우던, 앱의 특정 동작을 실행시키던, 원하는 대로 할 수 있게 되는 것. 그 내용까지 다루려면 너무 길어질 것 같아서, 그리고 앱마다 너무 달라서 생략하겠다.

생각보다 이미 너무 길어졌는데, 정리하자면
DeepLink 는 앱에서 링크의 형태로 원하는 동작을 실행할 수 있도록 하는 기능.
종류는 기존의 URL Scheme 방식과, Universal/App Link 방식이 있다. 일반적으로는 두 종류 다 동작하도록 작성하는 것이 권장.
Universal Links 방식으로 동작하려면 링크 검증을 위해 사전에 설정해야 하는 것들이 있다.

'공부 > TIL•기타' 카테고리의 다른 글

개발 반성회 (async / mvc)  (6) 2025.12.30
정적 / 동적 라이브러리  (0) 2025.12.18
CGI  (0) 2025.12.10
ORM Document / Docset  (0) 2025.12.01
Breakpoint  (0) 2025.11.25