{
"expo": {
"name": "My Commerce App",
"slug": "my-commerce-app",
"scheme": "mycommerce",
"ios": {
"bundleIdentifier": "ro.mycommerce.app",
"associatedDomains": [
"applinks:mycommerce.ro",
"applinks:www.mycommerce.ro"
]
},
"android": {
"package": "ro.mycommerce.app",
"intentFilters": [
{
"action": "VIEW",
"autoVerify": true,
"data": [
{
"scheme": "https",
"host": "mycommerce.ro",
"pathPrefix": "/product"
}
],
"category": ["BROWSABLE", "DEFAULT"]
}
]
}
}
}Deep linking-ul în React Native a fost mereu un coșmar de configurat pe nativ, dar Expo Router v3 a schimbat radical regula jocului prin file-based routing. În postarea asta vă arăt exact cum am configurat suportul hibrid (Custom Schemes + Universal/App Links) pe o aplicație de peste 40k utilizatori activi lunar, fără să ne prindem urechile în proiecte Xcode sau module de Java.
Custom Scheme vs. Universal Links: alegerea pragmatică
Multe echipe încep doar cu mycommerce://. E simplu, pui scheme: "mycommerce" în app.json și gata, funcționează din prima pe emulator. Dar ce faci când userul dă click pe un link de produs dintr-un email pe telefon și nu are aplicația instalată? În cazul scheme-urilor custom, sistemul de operare afișează o eroare urâtă sau nu face nimic.
De asta avem nevoie de Universal Links (iOS) și App Links (Android). Trade-off-ul e clar aici: custom scheme-ul îl configurezi în 2 minute, dar UX-ul e fragil. Universal Links oferă un fallback impecabil către site-ul web dacă app-ul lipsește, dar îți mănâncă lejer câteva ore (sau zile) din cauza verificărilor stricte de domenii, SSL și fișiere de asociere.
Ce face Expo Router diferit față de React Navigation clasic
Anterior, trebuia să-ți construiești manual un obiect uriaș de linking în React Navigation, unde mapai fiecare cale pe fiecare screen din stack. Dacă schimbai o cale în fișiere, uitați să actualizezi și obiectul de config.
Cu Expo Router, totul este bazat pe folderul app/. Ruta din fișierul app/product/[id].tsx va răspunde automat atât la https://mycommerce.ro/product/123, cât și la mycommerce://product/123. Nu mai scrii nicio linie de configurare JS pentru rute. Singurul lucru de care trebuie să te îngrijești este configurarea corectă din app.json pentru ca sistemele de operare să predea controlul către aplicație.
Belelele de pe producție: AASA cache și Android Intent Filters
La proiectul de ecommerce de care menționam, am pierdut aproape o zi întreagă din cauza cache-ului agresiv de la Apple. Apple validează fișierul apple-app-site-association (AASA) prin propriile servere CDN, nu direct de pe domeniul tău de fiecare dată când cineva instalează aplicația.
Dacă modifici ceva în AASA, CDN-ul Apple poate păstra versiunea veche până la 24 de ore. Ca să verifici dacă Apple a procesat corect fișierul tău, folosește direct endpoint-ul lor de test: https://app-site-association.cdn-apple.com/a/v1/domeniul-tau.ro.
La Android, fii atent la autoVerify: true în intentFilters. Dacă Google Play Domain Verification eșuează pentru că fișierul assetlinks.json nu returnează 200 OK cu Content-Type application/json (fără redirect-uri 301!), Android va ignora tăcut App Link-ul și va deschide Chrome fără să avertizeze utilizatorul.
Cum testezi rapid fără să faci build în store
Pentru iOS Simulator, nu încerca să introduci URL-ul în Safari că te încurcă. Deschide terminalul și rulează comanda nativă oferită de CLI-ul Expo:
npx uri-scheme open "https://mycommerce.ro/product/456" --ios
Iar pentru Android emulator:
adb shell am start -W -a android.intent.action.VIEW -d "https://mycommerce.ro/product/456"
Atât. Dacă configurația din app.json e corectă, sistemul va deschide instant ecranul corespunzător din aplicație și va popula automat useLocalSearchParams() cu id: "456".
Voi cum gestionați cazurile în care userul deschide un Universal Link de produs, dar nu este autentificat în aplicație? Redirecționați spre Login cu return-url sau îl lăsați pe ecran cu opțiune de read-only?