Deep link adalah link yang membuka satu layar tertentu di dalam aplikasi, bukan cuma membuka aplikasinya. Bu Rini mengirim pesan WhatsApp ke Mbak Wulan berisi link Order #13, dan ketukan di link itu langsung membuka layar Order #13. Bayangin nota cucian: "datang ke toko" itu membuka aplikasi, "ambil di rak 13" itu deep link. Dan kalau Mbak Wulan belum punya aplikasinya, link yang baik tetap membawanya ke tempat yang benar: halaman web Order #13.
🧪 Tebakan cepat: Universal Link sudah benar dan aplikasinya terpasang. Mbak Wulan mengetik
https://laundryburini.id/order/13di address bar Safari. Aplikasinya terbuka? Jawabannya ada di bagian "Universal Links di iOS".
Tiga jenis link ke Order #13
| Link | Jenis | Aplikasi terpasang | Belum terpasang |
|---|---|---|---|
laundryburini://order/13 |
custom scheme | Order #13 terbuka | gak ada yang bisa membukanya |
https://laundryburini.id/order/13 di iPhone |
Universal Link | Order #13 terbuka | halaman web Order #13 |
https://laundryburini.id/order/13 di Android |
App Link | Order #13 terbuka | halaman web Order #13 |
Custom scheme gampang dipasang, tapi aplikasi lain bisa mendaftarkan scheme yang sama, dan tanpa aplikasinya link itu buntu. Universal Link dan App Link adalah alamat HTTPS biasa yang dibuktikan milikmu lewat file di domainmu, jadi punya cadangan web.
Expo Router: link langsung ke layar
scheme: "laundryburini" sudah ada di app.json sejak pelajaran setup, dan Expo Router membuat rute dari file. Jadi tanpa konfigurasi apa pun, laundryburini://order/13 membuka src/app/order/[id].tsx dengan { id: '13' }. Expo Router menggabungkan host dan path dari link itu (order + /13) jadi order/13. Link HTTPS https://laundryburini.id/order/13 dibuang origin-nya, jadi /order/13, rute yang sama.
$ npx uri-scheme list
› iOS: Schemes for config: ./ios/LaundryBuRini/Info.plist
› laundryburini://
› id.laundryburini.app://
› Android: Schemes for config: ./android/app/src/main/AndroidManifest.xml
› laundryburini://
› https://
Itu hasil di project ini setelah npx expo prebuild. Prebuild juga mendaftarkan bundle ID sebagai scheme kedua, dan https:// muncul di Android karena App Link di bawah. Untuk mencoba di development build:
npx uri-scheme open laundryburini://order/13 --android
npx uri-scheme open laundryburini://order/13 --ios
adb shell am start -W -a android.intent.action.VIEW -d "laundryburini://order/13" id.laundryburini.app
Di Expo Go, scheme-nya milik Expo Go: exp://127.0.0.1:8081/--/order/13. Bagian setelah /--/ adalah path di aplikasimu. Link ke rute yang gak ada (misalnya laundryburini://pesanan/13) menampilkan layar "Unmatched Route" bawaan, atau layar milikmu di src/app/+not-found.tsx.
Universal Links di iOS
Dua sisi harus saling mengakui. Di aplikasi:
{ "expo": { "ios": { "bundleIdentifier": "id.laundryburini.app", "associatedDomains": ["applinks:laundryburini.id"] } } }
Prebuild menulisnya ke entitlements sebagai com.apple.developer.associated-domains berisi applinks:laundryburini.id (tanpa https://). Di web, file https://laundryburini.id/.well-known/apple-app-site-association:
{
"applinks": {
"details": [
{
"appIDs": ["A1B2C3D4E5.id.laundryburini.app"],
"components": [{ "/": "/order/*", "comment": "Detail order dibuka di aplikasi" }]
}
]
}
}
A1B2C3D4E5adalah Team ID akun Apple Developer-mu, diikuti bundle ID.- Nama filenya tanpa
.json, disajikan lewat HTTPS dengan sertifikat yang sah, tanpa redirect. - Sejak iOS 14, yang mengambil file ini adalah CDN Apple, bukan HP. CDN-nya memintanya dalam 24 jam, dan HP memeriksa ulang sekitar seminggu sekali. Setelah mengubah file, pasang ulang aplikasinya untuk menguji.
- Cara menguji: tempel link di aplikasi Notes lalu tekan lama. Kalau benar, ada pilihan buka di aplikasi.
Mengetik alamat langsung di address bar adalah navigasi di dalam browser, dan gak pernah membuka aplikasi. Safari membuka halaman webnya. Itu jawaban tebakannya. Universal Link bekerja saat link diketuk dari aplikasi lain: WhatsApp, email, Notes.
App Links di Android
{ "expo": { "android": { "package": "id.laundryburini.app", "intentFilters": [{
"action": "VIEW",
"autoVerify": true,
"data": [{ "scheme": "https", "host": "laundryburini.id", "pathPrefix": "/order" }],
"category": ["BROWSABLE", "DEFAULT"]
}] } } }
Prebuild menulis <intent-filter android:autoVerify="true"> dengan android:host="laundryburini.id" dan android:pathPrefix="/order" ke AndroidManifest.xml. Di web, https://laundryburini.id/.well-known/assetlinks.json, disajikan dengan content type application/json:
[{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": { "namespace": "android_app", "package_name": "id.laundryburini.app", "sha256_cert_fingerprints": ["SHA256_SERTIFIKAT_DARI_EAS_CREDENTIALS"] }
}]
- Fingerprint SHA-256 diambil dari sertifikat penandatangan aplikasi:
eas credentials -p android, atau Play Console bagian App signing. autoVerify: truemembuat Android memeriksa file itu saat aplikasi dipasang. Sejak Android 12, link HTTPS ke domain yang belum terverifikasi dibuka di browser, bukan di aplikasi.- Periksa dengan
adb shell pm get-app-links id.laundryburini.app(barislaundryburini.idharusverified), dan ulangi pemeriksaan denganadb shell pm verify-app-links --re-verify id.laundryburini.app.
Kalau aplikasinya belum terpasang
Universal Link dan App Link jatuh ke browser, jadi https://laundryburini.id/order/13 harus benar-benar ada sebagai halaman web: status Order #13 setelah login, plus ajakan memasang aplikasi. Di iPhone, <meta name="apple-itunes-app" content="app-id=..."> di halaman itu memunculkan banner App Store. Custom scheme gak punya cadangan seperti ini, jadi kirim link HTTPS di WhatsApp dan email, bukan laundryburini://.
Jangan percaya id dari link
Siapa pun bisa membuat link laundryburini://order/12, dan #12 adalah order Pak Darto. Layar detail memeriksa bentuk id-nya, lalu bertanya ke server, yang cuma menjawab order milik akun yang login:
// src/app/order/[id].tsx
export default function DetailOrder() {
const { id } = useLocalSearchParams<{ id: string }>();
const idSah = /^\d{1,6}$/.test(id ?? ''); // link bisa berisi apa saja
const { data, isPending, isError } = useQuery({
queryKey: ['orders', id],
queryFn: () => ambilOrder(id), // GET /api/orders/{id}: 404 untuk order orang lain
enabled: idSah,
retry: false,
});
if (!idSah || isError) return <Text>Order gak ketemu</Text>;
if (isPending) return <ActivityIndicator />;
return <Text>{data.nama}: {data.status}</Text>;
}
laundryburini://order/abcgagal di pemeriksaan bentuk, tanpa request.laundryburini://order/12lolos bentuknya, tapi server menjawab404, dan layar menampilkan "Order gak ketemu", bukan data Pak Darto.- Jangan pernah menampilkan isi link sebagai data (misalnya
?status=Siap diambil). Data selalu datang dari server.
⚠️ Jebakan yang sering ketemu
- Menguji Universal Link dengan mengetik di address bar. Itu gak pernah membuka aplikasi. Tempel di Notes, lalu ketuk.
https://diassociatedDomains. Tulisapplinks:laundryburini.idsaja.- File verifikasi lewat redirect atau tanpa HTTPS. CDN Apple menolak redirect, dan
assetlinks.jsonharus HTTPS denganapplication/json. - Fingerprint dari sertifikat yang salah. Build dari Play Store ditandatangani kunci Play App Signing, jadi masukkan fingerprint itu (
sha256_cert_fingerprintsboleh berisi lebih dari satu). - Percaya id dari link. Periksa bentuknya, dan biarkan server menjawab 404.
TL;DR: laundryburini://order/13 (custom scheme) langsung membuka src/app/order/[id].tsx lewat Expo Router, tanpa konfigurasi, tapi buntu kalau aplikasinya gak ada. https://laundryburini.id/order/13 adalah Universal Link (iOS: associatedDomains: ["applinks:laundryburini.id"] plus /.well-known/apple-app-site-association dengan appIDs dan components, HTTPS tanpa redirect, diambil CDN Apple) dan App Link (Android: intentFilters dengan autoVerify: true plus /.well-known/assetlinks.json berisi package dan fingerprint SHA-256), yang jatuh ke halaman web kalau aplikasinya belum terpasang. Uji dengan npx uri-scheme open, adb shell am start, dan adb shell pm get-app-links; Universal Link diuji dengan mengetuk dari Notes, karena mengetik di address bar gak pernah membuka aplikasi. Jangan percaya id dari link: periksa bentuknya dan biarkan GET /api/orders/{id} menjawab 404 untuk order orang lain.