URL의 맨 앞 scheme:// 부분이 스킴(scheme) 이다. 어떤 프로토콜/대상으로 이 URL을 처리할지 가리키는 이름표다.
https://example.com/user/42
└─┬─┘
scheme
example://user/42
└──┬──┘
scheme
https는 브라우저가 처리하도록 OS에 예약된 표준 스킴이고, example:// 같은 임의의 이름은 커스텀 URL 스킴(custom URL scheme) 이다. 앱이 "이 스킴은 내가 처리하겠다"고 OS에 등록해 두면, 그 스킴으로 시작하는 링크가 눌렸을 때 OS가 해당 앱을 띄워준다. 이게 딥링크의 가장 기본적인 형태다.
가장 헷갈리기 쉬운 지점이다. 스킴은 앱 코드(JS) 안이 아니라, 앱 번들의 메타데이터에 선언되고, 설치 시점에 OS에 등록된다.
Info.plistAndroidManifest.xml이 파일들은 JS 코드가 아니라 앱 패키지에 포함된 정적 메타데이터다. 앱을 설치하는 순간 OS가 이 파일을 읽어, 시스템 레지스트리에 "example:// 스킴 → 이 앱" 매핑을 저장한다. iOS는 Launch Services, Android는 PackageManager가 이 역할을 한다.
앱 설치
↓
OS가 Info.plist / AndroidManifest.xml 을 읽는다
↓
시스템 레지스트리에 "example:// → 이 앱" 등록
↓
(앱을 한 번도 실행하지 않아도 OS는 이미 매핑을 안다)
↓
나중에 example://user/42 링크가 눌리면 → OS가 앱을 깨운다
즉 OS는 앱에 "들어가서" 아는 게 아니다. 설치 때 미리 등록받아 두기 때문에, 앱이 실행 중이 아니어도, 한 번도 켜진 적 없어도 그 스킴이 어느 앱 것인지 이미 안다.
딥링크를 다루다 보면 스킴을 두 군데에 적게 되는데, 역할이 완전히 다르다.
| 적는 곳 | 누구를 위한 것인가 | 언제 쓰이나 |
|---|---|---|
Info.plist / AndroidManifest.xml | OS용 | 설치 시점, OS가 읽어 등록 |
React Navigation linking.prefixes | 앱 내부(JS)용 | OS가 URL을 앱에 넘긴 다음, 화면 매칭 |
이 둘은 별개 장소에 적히고, 같은 문자열이어야 링크가 끝까지 흐른다. 하나라도 어긋나면 링크가 앱에 도달하지 않거나, 도달해도 매칭되지 않는다.
example:// 같은 커스텀 스킴은 전역 소유권이 전혀 보장되지 않는다. 누구나 같은 스킴을 등록할 수 있다. 두 앱이 같은 스킴을 등록하면 이렇게 된다.
완화 관례로 역도메인 표기(com.mycompany.myapp://)를 써서 충돌 확률을 낮추긴 한다. 하지만 이건 어디까지나 충돌을 줄이는 관례일 뿐, OS가 강제하는 소유권 보장이 아니다. 마음먹은 다른 앱이 같은 문자열을 등록하는 걸 막을 방법은 없다.
또 다른 단점도 있다. 커스텀 스킴 링크는 앱이 설치돼 있지 않으면 아무 동작도 하지 않거나 에러가 된다(브라우저가 example://를 어떻게 처리할지 모른다). 웹 폴백이 없다.
커스텀 스킴의 소유권 문제를 푸는 방식이 유니버설 링크(Universal Links, iOS) 와 앱 링크(App Links, Android) 다. 커스텀 스킴 대신 https://example.com 같은 실제 도메인을 쓴다.
핵심은 도메인 소유권을 증명해야 한다는 것이다.
apple-app-site-association(AASA) 파일을 올린다.assetlinks.json 파일을 올린다.OS는 앱 설치 시(또는 검증 시점에) 그 도메인에 실제로 접속해 이 파일을 읽고, "이 앱이 이 도메인의 주인이 맞나"를 검증한다. 도메인은 전역적으로 유일하게 소유되므로, 다른 앱이 내 도메인 링크를 가로챌 수 없다. 충돌 자체가 성립하지 않는다.
추가 이점:
https:// URL이니까). 자연스러운 폴백이다.대신 비용이 있다. 도메인과 서버가 있어야 하고, AASA / assetlinks.json을 정확한 경로와 형식으로 호스팅해야 하며, 설정이 틀리면 검증이 조용히 실패해 디버깅이 까다롭다.
example:// 같은 단순 스킴, 실무에서 쓰나?공개 딥링크(사용자에게 공유되는 링크)로는 거의 쓰지 않는다. 충돌·하이재킹·웹 폴백 부재 때문에, 프로덕션의 공유 링크는 유니버설 링크 / 앱 링크로 가는 게 표준이다. 단순한 example://는 더더욱 안 쓰고, 쓰더라도 최소한 역도메인 형태로 바꾼다.
다만 커스텀 스킴이 여전히 유효하게 쓰이는 자리가 있다.
com.myapp://oauth/callback 처럼 인증 후 앱으로 돌아오는 리다이렉트 대상으로 흔하다. 외부에 공유되는 링크가 아니라 충돌 위험이 낮다.https:// 링크와 함께 등록해 두는 백업 경로.요약하면, 단순 example://를 공유 링크의 주력으로 쓰는 건 비권장이고, 콜백·개발·보조 용도로는 지금도 정상적으로 쓰인다.
| 방식 | 형태 | 소유권/충돌 | 미설치 시 | 설정 난이도 | 주 용도 |
|---|---|---|---|---|---|
| 단순 커스텀 스킴 | example:// | 보장 없음, 충돌 가능 | 동작 안 함 | 매우 쉬움 | 개발/테스트 |
| 역도메인 커스텀 스킴 | com.myapp:// | 충돌 확률만 낮춤 | 동작 안 함 | 쉬움 | OAuth 콜백, 보조 |
| 유니버설 링크 (iOS) | https://example.com | 도메인 검증으로 보장 | 웹으로 폴백 | 어려움 | 공개 공유 링크 |
| 앱 링크 (Android) | https://example.com | 도메인 검증으로 보장 | 웹으로 폴백 | 어려움 | 공개 공유 링크 |
공개 딥링크의 정답은 유니버설 링크(iOS) + 앱 링크(Android)다. 도메인 소유권으로 충돌과 하이재킹을 원천 차단하고, 미설치 시 웹 폴백까지 되기 때문이다. 사용자에게 공유되는 모든 링크는 이걸 기본으로 둔다.
그리고 커스텀 스킴(역도메인 형태)을 보조로 함께 등록하는 게 실무의 일반적인 조합이다. OAuth 콜백, 내부 진입점, 개발 편의 같은 곳에서 여전히 유용하기 때문이다.
React Navigation 입장에서는 둘을 구분하지 않는다. prefixes에 그냥 둘 다 적어두면, 어느 쪽으로 들어온 URL이든 동일한 화면 매칭 규칙을 탄다.
const linking = {
prefixes: [
'https://example.com', // 유니버설 링크 / 앱 링크 (주력, 공유용)
'com.mycompany.myapp://', // 커스텀 스킴 (보조, 콜백·개발용)
],
enabled: 'auto',
};
정리:
example://같은 단순 스킴은 학습·개발에는 편하지만 공개 링크의 주력으로는 부적합하다. 프로덕션 공유 링크는 유니버설/앱 링크를 주력으로, 역도메인 커스텀 스킴을 보조로 두는 조합이 가장 안전하다.
prefixes와 네이티브 등록은 항상 짝어떤 방식을 고르든 원칙은 같다. linking.prefixes에 적은 값과 네이티브에 등록한 스킴/도메인이 정확히 일치해야 링크가 끝까지 흐른다.
Info.plist(URL Types) / AndroidManifest.xml(intent-filter)에 등록한 스킴 = prefixes의 스킴autoVerify intent-filter + assetlinks.json(Android)에 설정한 도메인 = prefixes의 도메인OS 등록(설치 시점)과 앱 내부 매칭(prefixes)은 별개 단계이고, 둘 다 갖춰져야 동작한다는 점을 기억하면 된다.