Alle Artikel
mobiledeep linksiOSAndroid

Mobile-Deep-Link-Testing mit einem Localhost-Tunnel

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

  1. Liefere deine apple-app-site-association und assetlinks.json von deinem lokalen Dev-Server unter /.well-known/ aus. Beide Dateien. Beide Routen.
  2. Führe npx portpreview 3000 aus (oder welchen Port dein Dev-Server nutzt).
  3. Notiere den Tunnel-Hostnamen — etwa abc123.portpreview.dev.
  4. Füge im Associated-Domains-Entitlement deiner iOS-App applinks:abc123.portpreview.dev hinzu. Im Intent-Filter von Android denselben Host.
  5. Baue und installiere die App auf einem echten Gerät (Simulatoren haben seltsames Universal-Link-Verhalten).
  6. Ö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:

  1. Starte das Backend, das die .well-known-Dateien ausliefert.
  2. Tunnle es mit npx portpreview 3000.
  3. 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.
  4. QA auf physischen Geräten — sowohl Neuinstallationen als auch Updates.
  5. 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.

Häufig gestellte Fragen

Kann ich iOS Universal Links auf localhost testen?
Nicht direkt. Universal Links verlangen, dass iOS apple-app-site-association über echtes HTTPS von einer echten Domain holt. localhost qualifiziert sich nicht. Ein Tunnel gibt dir eine HTTPS-URL auf einer Tunnel-Subdomain, die iOS mit deiner App verknüpft, sobald du sie im Associated-Domains-Entitlement aufführst.
Welchen Content-Type sollte apple-app-site-association nutzen?
Modernes iOS erwartet application/json. Ältere Versionen akzeptierten auch application/pkcs7-mime für signierte Dateien. Die meisten Apps nutzen heute die reine JSON-Variante. Nimmt iOS deine Datei nicht auf, prüfe zuerst den Content-Type-Header aus einem Desktop-Browser.
Brauchen Android App Links einen Tunnel zum lokalen Testen?
Ja, wenn du volle App-Link-Verifizierung willst (keine Custom URL Schemes). Android holt assetlinks.json über echtes HTTPS von der Domain in deinem Intent-Filter. Ein Tunnel liefert eine echte HTTPS-URL auf einer Domain, die das Gerät verifizieren kann. Custom URL Schemes brauchen das nicht, sind aber weniger sicher.