Universal Links no iOS e App Links no Android exigem ambos um arquivo JSON servido por HTTPS na raiz do seu domínio. O iOS quer /.well-known/apple-app-site-association. O Android quer /.well-known/assetlinks.json. O localhost não consegue servir nenhum sobre um hostname que um SO associe ao seu app. Então tunelizamos.
O requisito real em cada plataforma
iOS Universal Links
O SO busca https://seu-dominio.com/.well-known/apple-app-site-association (ou /apple-app-site-association) quando seu app é instalado. Compara o conteúdo com o entitlement de domínios associados do seu app. A busca precisa ter sucesso sobre HTTPS real. O iOS não confia em certificados autoassinados para isso, nem no localhost.
Android App Links
O Android verifica os App Links buscando https://seu-dominio.com/.well-known/assetlinks.json. O arquivo precisa listar as fingerprints SHA-256 do seu app. A verificação acontece na instalação e no primeiro lançamento. A mesma restrição: HTTPS real, domínio real.
Por que o localhost não consegue fazer isso diretamente
Mesmo com o mkcert te dando HTTPS confiável no navegador do seu laptop, o SO móvel não confia na sua CA local. E o domínio no seu apple-app-site-association tem que bater com o domínio associado no entitlement do app — que é um domínio real resolúvel por DNS, não localhost.
Alguns times contornam isso com um domínio de staging dedicado. Funciona, mas o ciclo de iteração é lento. Um túnel localhost te dá uma URL HTTPS real num subdomínio real que os dispositivos móveis alcançam sem reclamar.
Como conectar
- Sirva seu
apple-app-site-associationeassetlinks.jsondo seu servidor de desenvolvimento local em/.well-known/. Ambos os arquivos. Ambas as rotas. - Rode
npx portpreview 3000(ou a porta que seu servidor de desenvolvimento usa). - Anote o hostname do túnel — algo como
abc123.portpreview.dev. - No entitlement de domínios associados do seu app iOS, adicione
applinks:abc123.portpreview.dev. No intent filter do Android, adicione o mesmo host. - Compile e instale o app num dispositivo real (simuladores têm comportamento estranho com Universal Links).
- Abra a URL a partir de outro app — Notas, Mail, um QR code — e veja-a rotear para o seu app instalado em vez do navegador.
O porém de tunelizar para deep links: o hostname do túnel muda entre sessões a menos que você tenha um subdomínio reservado. Cada rotação significa recompilar o app com a nova entrada de domínio associado. Se você itera no comportamento de deep links com frequência, consiga um subdomínio reservado.
O content-type importa para o apple-app-site-association
O iOS espera o arquivo sem extensão e com content-type application/json (iOS mais novo) ou application/pkcs7-mime (formato antigo, assinado). Quase todos os apps modernos usam a variante JSON simples. Garanta que seu servidor de desenvolvimento retorne o content-type certo ou o iOS rejeita o arquivo em silêncio sem erro útil.
Teste primeiro de um navegador desktop: abra https://seu-tunel.portpreview.dev/.well-known/apple-app-site-association e confirme que o JSON renderiza e o cabeçalho content-type nas dev tools está certo. Se estiver errado, corrija antes de caçar bugs de Universal Link no simulador.
As outras armadilhas de depuração de deep links
Incompatibilidade de entitlement do app
Se seu entitlement de domínios associados diz applinks:abc.portpreview.dev mas seu apple-app-site-association lista def.portpreview.dev, o iOS não busca. O hostname precisa ser consistente.
Universal Link de dentro do Safari
Tocar um Universal Link dentro do Safari (o mesmo app onde a aba está) às vezes abre no Safari em vez do app. Isso é por design — o iOS evita o truque de "abrir o app sempre que o usuário clica num link". Teste a partir das Notas ou do Mail.
Android App Links e digital asset links
O Android também suporta esquemas de URL personalizados (seuapp://caminho), que não precisam de HTTPS nem de assetlinks.json. São mais fáceis de testar mas menos seguros — qualquer app pode registrar o mesmo esquema. Para deep linking com qualidade de produção, os App Links são a resposta.
O fluxo de testes móveis que usamos
Para um projeto que entrega iOS e Android com deep links:
- Inicie o backend servindo os arquivos
.well-known. - Tunelize-o com
npx portpreview 3000. - Quando a URL do túnel estiver estável para a sessão (ou use um subdomínio reservado), atualize as entradas de domínio associado do app e compile.
- QA em dispositivos físicos — tanto instalações novas quanto atualizações.
- Para combos de OAuth-e-deep-link (provedores de login que redirecionam de volta para o app), combine isso com testes de callbacks OAuth.
A configuração é chata na primeira vez. Depois disso, a URL do túnel é só mais uma variável de ambiente no scheme do Xcode e do Gradle.
Para padrões mais amplos de testes móveis, veja testes móveis com um túnel localhost. Entre na lista de espera do PortPreview.