딥링크, 유니버셜 링크, 앱 링크 - 모바일 링크의 모든 것

2024년 12월 05일

dev-common

# 딥링크# 유니버셜링크# 앱링크# 모바일

개요

모바일 앱 개발이나 마케팅에서 자주 등장하는 용어들입니다. 비슷해 보이지만 각각 다른 개념이고, 동작 방식도 다릅니다.

앱 내 특정 화면으로 바로 이동하는 링크입니다.

URI Scheme 방식

가장 기본적인 딥링크 방식입니다.

myapp://product/123
myapp://user/profile
twitter://user?screen_name=elonmusk

장점

  • 구현이 간단함
  • 앱에서 직접 스킴 등록만 하면 됨

단점

  • 앱이 설치되어 있지 않으면 동작하지 않음 (에러 발생)
  • 스킴 충돌 가능 (다른 앱과 같은 스킴 사용 시)
  • 웹에서 검증 불가

동작 흐름

  1. 사용자가 myapp://product/123 클릭
  2. OS가 myapp 스킴을 처리할 앱 검색
  3. 앱이 있으면 → 앱 실행 + 해당 화면으로 이동
  4. 앱이 없으면 → 에러 또는 아무 반응 없음

**iOS 9+**에서 도입된 Apple의 딥링크 솔루션입니다. 일반 HTTPS URL을 사용합니다.

https://example.com/product/123

동작 원리

  1. 앱이 설치되어 있으면 → 앱에서 열림
  2. 앱이 없으면 → 웹 브라우저에서 해당 URL 열림 (Fallback)

설정 방법

1. 서버에 apple-app-site-association 파일 배포

// https://example.com/.well-known/apple-app-site-association
{
  "applinks": {
    "apps": [],
    "details": [
      {
        "appID": "TEAM_ID.com.example.app",
        "paths": ["/product/*", "/user/*"]
      }
    ]
  }
}

2. 앱에서 Associated Domains 설정

applinks:example.com

3. iOS가 앱 설치 시 해당 도메인에서 파일 다운로드 & 캐싱

특징

  • HTTPS만 지원
  • Apple CDN이 파일을 캐싱 (앱 설치/업데이트 시 갱신)
  • 같은 도메인의 웹페이지에서 링크 클릭 시 동작 안 함 (Safari 정책)

**Android 6.0+**에서 도입된 Google의 딥링크 솔루션입니다. 유니버셜 링크와 유사한 개념입니다.

https://example.com/product/123

설정 방법

1. 서버에 assetlinks.json 파일 배포

// https://example.com/.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:EF:..."]
  }
}]

2. AndroidManifest.xml에 intent-filter 추가

<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" />
</intent-filter>

특징

  • android:autoVerify="true" 설정 시 자동 검증
  • 검증 실패 시 일반 딥링크처럼 앱 선택 다이얼로그 표시

앱이 설치되어 있지 않아도 동작하는 딥링크입니다.

동작 흐름

  1. 사용자가 링크 클릭
  2. 앱이 없으면 → 앱스토어로 이동
  3. 앱 설치 후 첫 실행 시 → 원래 의도했던 화면으로 이동

구현 방식

  • 클립보드: 링크 정보를 클립보드에 저장 → 앱 실행 시 읽기
  • 핑거프린팅: 디바이스 정보 기반 매칭 (IP, User-Agent 등)
  • 서드파티 서비스: Branch, AppsFlyer, Firebase Dynamic Links 등

비교 표

구분 URI Scheme Universal Links (iOS) App Links (Android)
형식 myapp://path https://domain/path https://domain/path
앱 미설치 시 에러/무반응 웹으로 Fallback 웹으로 Fallback
검증 방식 없음 AASA 파일 assetlinks.json
보안 낮음 (스킴 탈취 가능) 높음 (도메인 소유권 검증) 높음 (도메인 소유권 검증)
최소 버전 - iOS 9+ Android 6.0+

실무 팁

1. 둘 다 설정하라

유니버셜 링크/앱 링크가 안 되는 상황이 있습니다:

  • 앱 내 웹뷰에서 클릭
  • 일부 SNS 앱의 인앱 브라우저
  • 사용자가 “브라우저에서 열기” 선택

→ URI Scheme을 백업으로 함께 구현

2. 테스트 도구

# iOS - AASA 파일 검증
curl -I https://example.com/.well-known/apple-app-site-association

# Android - Digital Asset Links 검증
https://developers.google.com/digital-asset-links/tools/generator

3. 디버깅

  • iOS: Settings > Developer > Universal Links 에서 진단
  • Android: adb shell am start -W -a android.intent.action.VIEW -d "https://example.com/path"

Flutter에서 딥링크 구현

Flutter에서는 여러 방식으로 딥링크를 처리할 수 있습니다.

1. go_router 사용 (권장)

// pubspec.yaml
dependencies:
  go_router: ^12.0.0
final router = GoRouter(
  routes: [
    GoRoute(
      path: '/product/:id',
      builder: (context, state) {
        final productId = state.pathParameters['id'];
        return ProductScreen(id: productId!);
      },
    ),
  ],
);

2. 네이티브 설정

iOS - Info.plist

<!-- URI Scheme -->
<key>CFBundleURLTypes</key>
<array>
  <dict>
    <key>CFBundleURLSchemes</key>
    <array>
      <string>myapp</string>
    </array>
  </dict>
</array>

iOS - Associated Domains (Runner.entitlements)

<key>com.apple.developer.associated-domains</key>
<array>
  <string>applinks:example.com</string>
</array>

Android - AndroidManifest.xml

<activity android:name=".MainActivity">
  <!-- URI Scheme -->
  <intent-filter>
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <data android:scheme="myapp" />
  </intent-filter>

  <!-- App Links -->
  <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" />
  </intent-filter>
</activity>

3. 앱 링크 수신 처리

import 'package:app_links/app_links.dart';

class DeepLinkHandler {
  final _appLinks = AppLinks();

  void init() {
    // 앱이 이미 실행 중일 때 딥링크 수신
    _appLinks.uriLinkStream.listen((Uri uri) {
      _handleDeepLink(uri);
    });

    // 앱이 딥링크로 시작된 경우
    _appLinks.getInitialAppLink().then((uri) {
      if (uri != null) {
        _handleDeepLink(uri);
      }
    });
  }

  void _handleDeepLink(Uri uri) {
    // /product/123 -> ProductScreen으로 이동
    if (uri.pathSegments.isNotEmpty && uri.pathSegments[0] == 'product') {
      final productId = uri.pathSegments[1];
      // Navigator 또는 GoRouter로 이동
    }
  }
}

4. 유용한 패키지

패키지 설명
go_router 선언적 라우팅 + 딥링크 자동 처리
app_links 딥링크 수신 처리 (uni_links 대체)
firebase_dynamic_links Firebase Dynamic Links 연동

5. 테스트 방법

# iOS 시뮬레이터
xcrun simctl openurl booted "myapp://product/123"
xcrun simctl openurl booted "https://example.com/product/123"

# Android 에뮬레이터
adb shell am start -W -a android.intent.action.VIEW -d "myapp://product/123"
adb shell am start -W -a android.intent.action.VIEW -d "https://example.com/product/123"

관련 용어

  • Deferred Deep Link: 앱 미설치 상태에서도 설치 후 특정 화면으로 이동
  • Dynamic Links (Firebase): Google의 디퍼드 딥링크 솔루션 (2025년 지원 종료 예정)
  • Branch.io: 대표적인 딥링크 서드파티 서비스

참고 자료

© 2025, 미나리와 함께 만들었음