Flutter 개발

Flutter 딥링크 디버깅: 안 되는 이유와 해결법

Universal Links, App Links 삽질 기록

John Doe
2026년 4월 18일
6분 읽기
45

딥링크가 안 되는 흔한 이유

딥링크 설정은 한 곳이라도 빠지면 전체가 안 되죠. iOS와 Android 각각 설정 파일, 서버 파일, 앱 코드 세 곳을 모두 맞춰야 합니다.
가장 흔한 실수, AASA(Apple App Site Association) 파일의 JSON 오류이죠.

iOS Universal Links 체크리스트

Associated Domains 설정, AASA 파일 호스팅, 앱 entitlements 파일. 이 세 가지가 모두 일치해야 합니다.
json
// 1. Runner.entitlements에 도메인 추가
// Xcode > Signing & Capabilities > Associated Domains
//   applinks:example.com

// 2. AASA 파일 (서버: https://example.com/.well-known/apple-app-site-association)
{
  "applinks": {
    "apps": [],
    "details": [
      {
        "appIDs": ["TEAM_ID.com.example.myapp"],
        "components": [
          {
            "/": "/games/*",
            "comment": "게임 상세 딥링크"
          }
        ]
      }
    ]
  }
}

// 흔한 실수:
// - appIDs에 TeamID 빠뜨림
// - AASA 파일 Content-Type이 application/json이 아님
// - https가 아닌 http로 호스팅

Android App Links 검증

assetlinks.json 파일과 AndroidManifest.xml의 intent-filter가 일치해야 합니다. SHA256 인증서 핑거프린트 불일치가 가장 흔한 원인입니다.
yaml
<!-- AndroidManifest.xml -->
<activity android:name=".MainActivity">
  <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="/games" />
  </intent-filter>
</activity>
json
// .well-known/assetlinks.json
[{
  "relation": ["delegate_permission/common.handle_all_urls"],
  "target": {
    "namespace": "android_app",
    "package_name": "com.example.myapp",
    "sha256_cert_fingerprints": [
      "AA:BB:CC:..."  // debug와 release 키가 다름!
    ]
  }
}]

// SHA256 핑거프린트 확인
// keytool -list -v -keystore ~/.android/debug.keystore -alias androiddebugkey

adb로 딥링크 테스트

실제 브라우저에서 링크를 탭하는 것과 adb로 인텐트를 보내는 것은 동작이 다릅니다. 둘 다 테스트해야 돼요.
bash
# Android 딥링크 테스트
adb shell am start -a android.intent.action.VIEW \
  -c android.intent.category.BROWSABLE \
  -d "https://example.com/games/detail?gameId=abc123"

# App Links 검증 상태 확인
adb shell pm get-app-links com.example.myapp

# iOS 딥링크 테스트 (시뮬레이터)
xcrun simctl openurl booted "https://example.com/games/detail?gameId=abc123"

GoRouter redirect와 딥링크 충돌

인증 체크를 위한 redirect 로직이 딥링크를 가로채는 경우가 있습니다. 앱이 아직 초기화 중일 때 딥링크가 들어오면 redirect가 먼저 실행되죠.
해결법은 딥링크 경로를 저장해두고, 인증 완료 후 이동하는 겁니다.
Dart
// redirect에서 딥링크 보존
String? _pendingDeepLink;

redirect: (context, state) {
  final isLoggedIn = ref.read(authProvider).isLoggedIn;
  final location = state.matchedLocation;
  final isAuthRoute = location == '/login' || location == '/signup';

  if (!isLoggedIn && !isAuthRoute) {
    // 딥링크 경로 저장
    _pendingDeepLink = state.uri.toString();
    return '/login';
  }

  // 로그인 완료 후 저장된 딥링크로 이동
  if (isLoggedIn && isAuthRoute && _pendingDeepLink != null) {
    final link = _pendingDeepLink;
    _pendingDeepLink = null;
    return link;
  }

  return null;
}

Firebase Dynamic Links 대안

Firebase Dynamic Links는 2025년 8월에 종료되었어요. 대안으로 Branch.io나 직접 구현을 고려해야 돼요.

단순한 딥링크라면 직접 AASA/assetlinks.json을 호스팅하는 게 가장 깔끔해요. 어트리뷰션 추적까지 필요하면 Branch.io나 Adjust를 쓰세요.
#딥링크
#Universal Links
#App Links
#GoRouter
#Flutter
#디버깅