Universal Links auf iOS und App Links auf Android verlangen beide eine über HTTPS ausgelieferte JSON-Datei im Root deiner Domain. iOS will /.well-known/apple-app-site-association. Android will /.well-known/assetlinks.json. localhost kann keine davon über einen Hostnamen ausliefern, den ein OS mit deiner App verknüpft. Also tunneln wir.
Die konkrete Anforderung pro Plattform
iOS Universal Links
Das OS holt https://deine-domain.com/.well-known/apple-app-site-association (oder /apple-app-site-association), wenn deine App installiert wird. Es prüft den Inhalt gegen das Associated-Domains-Entitlement deiner App. Der Abruf muss über echtes HTTPS gelingen. iOS vertraut dafür keinen self-signed Zertifikaten und nicht localhost.
Android App Links
Android verifiziert App Links durch Abruf von https://deine-domain.com/.well-known/assetlinks.json. Die Datei muss die SHA-256-Fingerprints deiner App auflisten. Die Verifizierung passiert bei der Installation und beim ersten Start. Dieselbe Einschränkung: echtes HTTPS, echte Domain.
Warum localhost das nicht direkt kann
Selbst wenn mkcert dir vertrautes HTTPS im Browser deines Laptops gibt, vertraut das mobile OS deiner lokalen CA nicht. Und die Domain in deiner apple-app-site-association muss zur Associated-Domain im App-Entitlement passen — das ist eine echte, DNS-auflösbare Domain, nicht localhost.
Manche Teams umgehen das mit einer dedizierten Staging-Domain. Das funktioniert, aber die Iterationsschleife ist langsam. Ein localhost-Tunnel gibt dir eine echte HTTPS-URL auf einer echten Subdomain, die mobile Geräte klaglos erreichen.
So verkabelst du es
- Liefere deine
apple-app-site-associationundassetlinks.jsonvon deinem lokalen Dev-Server unter/.well-known/aus. Beide Dateien. Beide Routen. - Führe
npx portpreview 3000aus (oder welchen Port dein Dev-Server nutzt). - Notiere den Tunnel-Hostnamen — etwa
abc123.portpreview.dev. - Füge im Associated-Domains-Entitlement deiner iOS-App
applinks:abc123.portpreview.devhinzu. Im Intent-Filter von Android denselben Host. - Baue und installiere die App auf einem echten Gerät (Simulatoren haben seltsames Universal-Link-Verhalten).
- Öffne die URL aus einer anderen App — Notizen, Mail, ein QR-Code — und beobachte, wie sie zu deiner installierten App statt zum Browser routet.
Der Haken beim Tunneln für Deep Links: Der Tunnel-Hostname ändert sich zwischen Sessions, sofern du keine reservierte Subdomain hast. Jede Rotation bedeutet, die App mit dem neuen Associated-Domain-Eintrag neu zu bauen. Iterierst du häufig am Deep-Link-Verhalten, hol dir eine reservierte Subdomain.
Der Content-Type ist wichtig für apple-app-site-association
iOS erwartet die Datei ohne Endung und entweder mit Content-Type application/json (neueres iOS) oder application/pkcs7-mime (älteres, signiertes Format). Fast alle modernen Apps nutzen die reine JSON-Variante. Stelle sicher, dass dein Dev-Server den richtigen Content-Type zurückgibt, sonst lehnt iOS die Datei stillschweigend ohne nützlichen Fehler ab.
Teste zuerst aus einem Desktop-Browser: Öffne https://dein-tunnel.portpreview.dev/.well-known/apple-app-site-association und bestätige, dass das JSON rendert und der Content-Type-Header in den Dev-Tools stimmt. Stimmt der nicht, behebe das, bevor du Universal-Link-Bugs im Simulator jagst.
Die anderen Deep-Link-Debugging-Fallen
App-Entitlement-Mismatch
Sagt dein Associated-Domains-Entitlement applinks:abc.portpreview.dev, aber deine apple-app-site-association listet def.portpreview.dev, holt iOS nicht. Der Hostname muss konsistent sein.
Universal Link aus Safari heraus
Tippst du einen Universal Link in Safari an (derselben App, in der der Tab ist), öffnet er manchmal in Safari statt in der App. Das ist Absicht — iOS verhindert den Trick „App öffnen, sobald der Nutzer auf einen Link klickt". Teste stattdessen aus Notizen oder Mail.
Android App Links und Digital Asset Links
Android unterstützt auch Custom URL Schemes (deineapp://pfad), die kein HTTPS oder assetlinks.json brauchen. Die sind einfacher zu testen, aber weniger sicher — jede App kann dasselbe Schema registrieren. Für produktionsreifes Deep Linking sind App Links die Antwort.
Mobile-Testing-Flow, den wir nutzen
Für ein Projekt, das iOS und Android mit Deep Links ausliefert:
- Starte das Backend, das die
.well-known-Dateien ausliefert. - Tunnle es mit
npx portpreview 3000. - Sobald die Tunnel-URL für die Session stabil ist (oder nutze eine reservierte Subdomain), aktualisiere die Associated-Domain-Einträge der App und baue.
- QA auf physischen Geräten — sowohl Neuinstallationen als auch Updates.
- Für OAuth-und-Deep-Link-Kombis (Sign-in-Provider, die zurück in die App redirecten) kombiniere das mit OAuth-Callback-Testing.
Das Setup ist beim ersten Mal nervig. Danach ist die Tunnel-URL nur noch eine weitere Env-Var im Xcode- und Gradle-Scheme.
Für breitere Mobile-Testing-Muster siehe Mobile-Testing mit einem localhost-Tunnel. Tritt der PortPreview-Warteliste bei.