eduardweb.
Navigație & StateAvansat#react-native#android#ios#deep-linking#expo-router

Deep linking universal cu Expo Router: Custom schemes și Universal Links fără bătăi de cap

De Sorin Tudor, 6 aug. 2026 · 7 vizualizări · 3 like-uri

Postat 6 aug. 2026
json
{
  "expo": {
    "name": "Aplicatia Mea",
    "slug": "my-app",
    "scheme": "myapp",
    "ios": {
      "bundleIdentifier": "ro.domeniu.myapp",
      "associatedDomains": ["applinks:domeniu.ro"]
    },
    "android": {
      "package": "ro.domeniu.myapp",
      "intentFilters": [
        {
          "action": "VIEW",
          "autoVerify": true,
          "data": [
            {
              "scheme": "https",
              "host": "domeniu.ro",
              "pathPrefix": "/produs"
            }
          ],
          "category": ["BROWSABLE", "DEFAULT"]
        }
      ]
    }
  }
}

Am migrat recent un proiect cu peste 40.000 de utilizatori activi de la React Navigation clasic la Expo Router v3. Se lăudau în docuri că deep linking-ul "just works", dar când am dat de terenul minat numit Apple App Site Association (AASA) și Android App Links pe Android 12+, am pierdut două zile întregi cu bug-uri dubioase.

Nu e complicat odată ce înțelegi mecanismul, dar detaliile mici te omoară.

Custom schemes vs. Universal Links: de ce le vrei pe ambele

La început, toți facem greșeala să punem doar un custom scheme de genul myapp://. E simplu, merge impecabil în mediul de dev și dă senzația că ești gata de lansare. Dar când trimiți un email de marketing sau un link de resetare de parolă pe WhatsApp, surpriză: WhatsApp nu transformă myapp:// într-un link dintr-un motiv întemeiat de securitate.

Ai nevoie neapărat de Universal Links (iOS) și App Links (Android). Vorbim de URL-uri HTTPS normale (https://domeniu.ro/produs/123) care deschid aplicația dacă e instalată, sau fac fallback pe browser dacă nu e.

Trade-off-ul e evident. Setup-ul pe serverul tău web este extrem de rigid. Dacă ai greșit un singur MIME type sau un header HTTP pe server, iOS refuză silențios să asocieze domeniul și stai să faci debug ore întregi prin console logs conectat la macOS.

Configurarea în app.json (partea ușoară)

Expo Router folosește structura fișierelor din folderul app/ pentru a mapa automat URL-urile. Asta înseamnă că n-ai nevoie de un obiect gigantic de linking cum făceam în React Navigation 5 sau 6. Router-ul știe direct că https://domeniu.ro/produs/123 trebuie să meargă în app/produs/[id].tsx.

Tot ce trebuie să faci în config e să declari scheme, ios.associatedDomains și android.intentFilters.

Atenție mare la iOS: elementul din associatedDomains trebuie să aibă neapărat prefixul applinks:. Dacă scrii doar domeniul fără prefix, Xcode nu va genera entitlement-ul corect la build-ul de producție.

Unde se rupe filmul: AASA și assetlinks.json

Serverul tău web trebuie să servească două fișiere fără nicio redirectare 301/302 și cu header-e HTTP perfect curate:

  1. iOS: https://domeniu.ro/.well-known/apple-app-site-association

    • Fără extensie .json în URL! M-am prins de asta după 3 ore de înjurături.
    • Content-Type trebuie să fie neapărat application/json.
    • Apple folosește un CDN propriu pentru caching. Când schimbi fișierul pe server, poate dura până la 24 de ore până când iOS-ul utilizatorilor își face re-fetch.
  2. Android: https://domeniu.ro/.well-known/assetlinks.json

    • Ai nevoie de amprenta SHA-256 a certificatului de semnare.
    • Capcană uriașă: dacă folosești Google Play App Signing, SHA-256-ul din keystore-ul tău local NU e cel de producție! Trebuie să intri în Google Play Console -> App Integrity și să iei de acolo SHA-256 App Signing Certificate. Am pierdut 6 ore pentru că testam build-ul trimis prin Internal Testing și link-urile se deschideau tot în Chrome.

Cum testezi fără să-ți smulgi părul

Pe iOS Simulator nu te baza pe Safari când tastezi URL-ul, fiindcă Safari ignoră Universal Links dacă scrii direct în address bar. Deschide terminalul și rulează comanda CLI de simulator.

Pe Android, folosește adb pentru a simula intenția direct în sistemul de operare. Dacă aveți autoVerify: true în intent filter, Android va încerca să descarce assetlinks.json la instalarea APK-ului. Dacă eșuează, fallback-ul se face pe browser fără niciun avertisment.

Dacă totul e configurat bine, Expo Router preia parametrul din drumul fișierului. În componenta app/produs/[id].tsx, apelezi doar useLocalSearchParams() și ai acces direct la id fără să scrii niciun listener manual de Linking.addEventListener.

Expo Router elimină 80% din boilerplate-ul de navigație, dar nu te scapă de strictețea cu care Apple și Google validează domeniile. Voi ce soluții folosiți pentru fallback când utilizatorul nu are aplicația instalată? Mergeți pe deferred deep linking cu Branch/AppsFlyer sau aveți un landing page custom?

Răspunsuri 0

Se încarcă răspunsurile…

Loghează-te pentru a răspunde

Doar membrii comunității pot lăsa comentarii.