Universal Link iOS dünyasının “standard deep linking” çözümü. Apple 2015’ten beri bu yaklaşımı iterek diyor. Setup “basit” görünüyor: apple-app-site-association dosyası koy, entitlement ekle, app delegate’te handle et.
Ama production’a aldığında sürprizler başlıyor. 5 uygulamada Universal Link kurguladım ve her seferinde Apple’ın documentation’ının atladığı şeyler ortaya çıktı. Bu yazıda 4 önemli gotcha’yı anlatacağım.
1. apple-app-site-association caching
Senaryo: Universal Link kurdun, test ediyorsun, çalışmıyor. Dosyayı düzelttin, tekrar test, hâlâ çalışmıyor. Delete app, reinstall, hâlâ eski davranış.
Sebep: iOS apple-app-site-association dosyasını agresif cache’liyor. Ve bu cache:
– Device-wide
– ~7 gün kadar canlı kalıyor
– Farklı invalidation mekanizmaları tam belgelenmemiş
Gerçek hayat fix’leri:
A. Associated Domains entitlement’ında query string. applinks:example.com?mode=developer kullanarak development mode’da invalidation daha agresif oluyor. Test Flight için değişiklik çok hızlı görünür.
B. Install/reinstall cycle. App’i uninstall et, cihazı restart et, app’i tekrar install et. Cache temizleniyor.
C. Farklı domain kullan. .dev subdomain veya test domain’inde çalış. Production domain’e 7 gün beklemeden geri dönülemiyor.
Bu cache behavior’u anlamak stress’ini çok azaltıyor. Development flow’una göre planlayın.
2. SwiftUI NavigationStack ile handle karmaşıklığı
iOS 16’dan önce AppDelegate’de application(_:continue:restorationHandler:) yeterliydi. SwiftUI App lifecycle’da bu biraz değişti:
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.onOpenURL { url in
handleUniversalLink(url)
}
}
}
}Problem: onOpenURL her çağrıldığında app state’i yok etmiyorsun. Kullanıcı bir ekrandaydı, link geldi, hangi ekrana gidecek? NavigationStack path’ini manipüle etmek gerekiyor.
Çözüm: app-wide bir URLRouter singleton kurgulamak.
@Observable
class URLRouter {
var pendingDeepLink: URL?
var path = NavigationPath()
func handle(_ url: URL) {
// Parse URL, update path
if let component = parseURL(url) {
path = NavigationPath([component])
}
}
}ContentView’da path’i bind et. onOpenURL router’ın handle() fonksiyonunu çağır.
3. Universal Link ile Safari bypassing
Apple “kullanıcı browser’da example.com/user/123 linkine tıklarsa app’e gitsin” demişti. Ama bazı senaryolarda bu çalışmıyor:
- Safari’de long press menüsünden “Open” → app açmıyor, web’e gidiyor
- QR kod scanner’dan açarsan → bazen app, bazen web
- Mail app’teki link → app’e gidiyor
- WhatsApp’taki link → app-e gidiyor
- Notion, Telegram gibi bazı uygulamalar → webview’da açıyor, app bypass
Apple’ın intention’ı iyiniyetli ama user experience inconsistent. Kullanıcı “web’de açıldı” şikayetleri için hazır ol.
Fix: her önemli web sayfasında “Open in App” smart banner koy. Apple’ın smart app banner meta tag’i:
<meta name="apple-itunes-app" content="app-id=123456, app-argument=example.com/deeplink">Safari bu banner’ı gösteriyor, kullanıcı explicit olarak app’e geçebiliyor.
4. Multiple domain handling
App bir domain’den (example.com) link bekleyebilir. Ama:
– Production: example.com
– Staging: staging.example.com
– PR preview: pr-456.example.com
– Test: test.example.com
Her domain için apple-app-site-association dosyası + Associated Domains entitlement’i.
Entitlement:
<array>
<string>applinks:example.com</string>
<string>applinks:staging.example.com</string>
<string>applinks:test.example.com</string>
</array>Problem: Her domain için ayrı build mu? Yoksa tüm domain’leri her build’de mi koyuyorsun?
Ben pragmatik yaklaşıyorum: tüm domain’leri tüm build’lerde. Production app staging.example.com linklerini de handle ediyor, ama staging endpoint’lerine sadece authenticated testler için gidiyor. Security risk minimum, development flow kolay.
Sub-domain wildcard (iOS 13+):
<string>applinks:*.example.com</string>Bu wildcard tüm subdomain’leri cover ediyor. PR preview’lar için ideal.
5. Deferred deep linking
Kullanıcı linke tıklıyor, app install yok, App Store açılıyor. Kullanıcı install ediyor, app açıyor. App kullanıcıyı doğru sayfaya götürmeli ama link bilgisi kayboldu.
Universal Link bunu out-of-box sağlamıyor. Facebook’un eski Deferred Deep Link patenti bir zamanlar buna erişimi kontrol ediyordu.
Typical çözüm: Branch, AppsFlyer gibi third-party SDK’lar. Link’i clipboard’a kopyalıyor, app açılınca clipboard’dan okuyor. iOS 15’te clipboard access için izin gerekiyor, UX bozuluyor.
Daha iyi yaklaşım: App Clip + Universal Link kombinasyonu. App Clip URL aynı zamanda Universal Link. Kullanıcı app Clip ile hızlı iş yapıyor, sonra full app download edebilir.
Bu complexity mostly değmiyor. Simple solution: web sayfasında QR kod göster + kullanıcıya “app’i indir, sonra bu QR’u tara” demek. Manuel ama güvenilir.
Test etmek
Universal Link test’i tuzaklı. Checklist:
- Safari’de paste & enter → app açılıyor mu? (doğrudan tıklama değil, adres çubuğuna yaz)
- Notes uygulamasından link → app açılıyor mu? (Safari dışı)
- Notification içindeki link → APNs payload’unda URL
- Mesajlaşma uygulamasından → WhatsApp, Telegram, Messages
- Email içindeki link → Mail.app, Gmail, Outlook
- QR kod’dan açılan link → Camera app
- Background app’den gelen link → app zaten açıkken link geliyor
- Cold start → app tamamen kapalıyken link geliyor
Her senaryoda farklı davranabilir. En azından 1, 2, 3, 8 test edilmeli.
Monitoring
Production’da Universal Link başarı oranını ölç:
- Web sayfalarında “app’e yönlendirildi mi” tracking (Apple’ın smart banner metric’leri)
- App’te deep link ile açılış sayısı (custom analytics event)
- Deep link ile açılanların conversion’ı vs organic
Bu metrikler Universal Link kurgusunun işe yarayıp yaramadığını gösteriyor. Eğer %5 altında “app’te açıldı” oranı varsa kullanıcı experience’ında bir yerlerde engel var.
Sonuç
Universal Link setup’ı basit, debugging’i karmaşık. AASA caching, SwiftUI integration, multiple domain, bypass senaryoları – bunlar Apple’ın documentation’ında yok veya az. Production’a aldığında karşılaşacaksın.
Sabırlı ol, test matrix’ini hazırla, monitoring koy. 1-2 hafta sonra çalışan bir sistemin olur. Ondan sonra pattern’i bütün projelerinde copy-paste ediyorsun, iş kolaylaşıyor.