Deep Linking y Universal Links en React Native con Expo Router: Guía Completa de Configuración

En el desarrollo de aplicaciones móviles comerciales (e-commerce, banca, reservas o redes sociales), la retención de usuarios depende críticamente de la continuidad entre los canales web y móvil. Cuando un usuario hace clic en un enlace recibido por correo, WhatsApp o un anuncio de marketing, la aplicación instalada en su dispositivo debe abrirse directamente en la pantalla del producto o pedido correspondiente (Deep Linking) en lugar de redirigirlo a un navegador web genérico. En esta guía técnica explicamos cómo configurar Universal Links en iOS (AASA), App Links en Android (
assetlinks.json) y el enrutamiento tipado con Expo Router.

Tipos de Enlaces en Aplicaciones Móviles#
Existen tres modalidades de deep linking en el ecosistema móvil moderno:
| Tipo de Enlace | Esquema de URL | Comportamiento en iOS / Android | Ventaja Principal |
|---|---|---|---|
| Custom URL Schemes | myapp://pedidos/123 |
Abre la app si está instalada; genera error si no está instalada. | Fácil configuración local de depuración. |
| Universal Links (iOS) | https://brayan.es/pedidos/123 |
Abre la app sin mostrar alertas intermedias. Si no está instalada, abre la web. | Máxima seguridad con certificados y dominio verificado. |
| App Links (Android) | https://brayan.es/pedidos/123 |
Abre la app de forma directa sin el diálogo selector de navegador. | Sin cuadros de diálogo confusos en Android. |
[Usuario hace clic en https://brayan.es/producto/4092]
│
┌──────────────┴──────────────┐
▼ ▼
[App Instalada] [App No Instalada]
│ │
▼ ▼
[Abre Pantalla Nativa] [Abre Web o Redirige a App Store]
(Expo Router: app/producto/[id].tsx)
1. Configuración de Dominios en app.json (Expo)#
En el archivo de configuración de Expo definimos los esquemas y las asociaciones de dominio tanto para iOS (Associated Domains) como para Android (Intent Filters):
{
"expo": {
"name": "MiAppEmpresarial",
"slug": "mi-app-empresarial",
"scheme": "myapp",
"ios": {
"bundleIdentifier": "com.empresa.miapp",
"supportsTablet": true,
"associatedDomains": [
"applinks:brayan.es",
"applinks:www.brayan.es"
]
},
"android": {
"package": "com.empresa.miapp",
"intentFilters": [
{
"action": "VIEW",
"autoVerify": true,
"data": [
{
"scheme": "https",
"host": "brayan.es",
"pathPrefix": "/pedidos"
},
{
"scheme": "https",
"host": "brayan.es",
"pathPrefix": "/producto"
}
],
"category": ["BROWSABLE", "DEFAULT"]
}
]
},
"plugins": [
"expo-router"
]
}
}
2. Publicación de Archivos de Verificación en el Servidor Web (Next.js)#
Tanto Apple como Google requieren que el dominio web aloje archivos estáticos de verificación bajo la ruta .well-known/.
2.1 Apple App Site Association (apple-app-site-association)#
En Next.js creamos un Route Handler en app/.well-known/apple-app-site-association/route.ts con cabecera Content-Type: application/json:
// src/app/.well-known/apple-app-site-association/route.ts
import { NextResponse } from "next/server";
export async function GET() {
const aasa = {
applinks: {
apps: [],
details: [
{
appID: "TEAMID12345.com.empresa.miapp", // Team ID de Apple + Bundle ID
paths: ["/pedidos/*", "/producto/*", "/cuenta/*"],
},
],
},
webcredentials: {
apps: ["TEAMID12345.com.empresa.miapp"],
},
};
return NextResponse.json(aasa, {
headers: {
"Content-Type": "application/json",
"Cache-Control": "public, max-age=86400",
},
});
}
2.2 Digital Asset Links para Android (assetlinks.json)#
Creamos el Route Handler en app/.well-known/assetlinks.json/route.ts con la huella digital SHA-256 del certificado de firma del APK o App Bundle:
// src/app/.well-known/assetlinks.json/route.ts
import { NextResponse } from "next/server";
export async function GET() {
const assetLinks = [
{
relation: ["delegate_permission/common.handle_all_urls"],
target: {
namespace: "android_app",
package_name: "com.empresa.miapp",
sha256_cert_fingerprints: [
"14:6D:E9:01:07:52:68:40:EE:80:...", // SHA-256 de Google Play Console / Keystore
],
},
},
];
return NextResponse.json(assetLinks, {
headers: {
"Content-Type": "application/json",
"Cache-Control": "public, max-age=86400",
},
});
}
3. Captura y Navegación Dinámica en Expo Router#
Gracias al sistema de enrutamiento basado en archivos de Expo Router, las rutas web coinciden de forma natural con la jerarquía de carpetas de la app móvil.
Estructura de archivos:
app/
├── _layout.tsx
├── index.tsx
└── producto/
└── [id].tsx
En la pantalla app/producto/[id].tsx, capturamos los parámetros de la URL sin importar si el usuario llegó desde la navegación interna o desde un enlace externo:
// app/producto/[id].tsx
import { View, Text, StyleSheet, ActivityIndicator } from "react-native";
import { useLocalSearchParams, Stack } from "expo-router";
import { useQuery } from "@tanstack/react-query";
import { fetchProductById } from "@/services/api";
export default function ProductDetailScreen() {
const { id } = useLocalSearchParams<{ id: string }>();
const { data: product, isLoading, error } = useQuery({
queryKey: ["product", id],
queryFn: () => fetchProductById(id),
enabled: !!id,
});
if (isLoading) {
return (
<View style={styles.center}>
<ActivityIndicator size="large" color="#0284c7" />
</View>
);
}
if (error || !product) {
return (
<View style={styles.center}>
<Text style={styles.errorText}>No se pudo cargar la información del producto.</Text>
</View>
);
}
return (
<View style={styles.container}>
<Stack.Screen options={{ title: product.title, headerBackTitle: "Atrás" }} />
<Text style={styles.title}>{product.title}</Text>
<Text style={styles.price}>S/ {product.price.toFixed(2)}</Text>
<Text style={styles.description}>{product.description}</Text>
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, padding: 20, backgroundColor: "#fff" },
center: { flex: 1, justifyContent: "center", alignItems: "center" },
title: { fontSize: 24, fontWeight: "bold", color: "#0f172a" },
price: { fontSize: 20, color: "#16a34a", marginVertical: 10, fontWeight: "600" },
description: { fontSize: 16, color: "#475569", lineHeight: 24 },
errorText: { color: "#dc2626", fontSize: 16 },
});
4. Pruebas y Validación de Deep Linking#
Para probar los enlaces en simuladores y dispositivos reales sin compilar builds de producción completas:
# Probar con Custom Scheme en iOS Simulator
npx uri-scheme open "myapp://producto/4092" --ios
# Probar con Universal Links en Android Emulator
npx uri-scheme open "https://brayan.es/producto/4092" --android
Conclusión#
Implementar Universal Links y App Links con Expo Router eleva drásticamente la calidad de tus aplicaciones móviles, facilitando campañas de marketing eficaces, pasarelas de pago externas con retorno fluido y una experiencia de usuario sin fricciones.
¿Estás desarrollando una aplicación móvil en iOS y Android con React Native y necesitas optimizar su arquitectura o enrutamiento avanzado? Revisa nuestros servicios en Desarrollo de Apps Móviles o contáctanos para una asesoría especializada.
¿Te gustaría profundizar en estos temas?
Aprende sobre desarrollo de software, apps a medida, automatizaciones con N8N, Next.js y Cloud con casos reales.


