Volver al Blog
AppsDesarrolloSoftware

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

Brayan Developer
5 min de lectura
Deep Linking y Universal Links en React Native con Expo Router: Guía Completa de Configuración
Aprende a configurar Universal Links en iOS y App Links en Android con Expo Router y Next.js para abrir pantallas internas de tu app desde la web de forma fluida.

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.

Portada

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",
    },
  });
}

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.

Etiquetas
React NativeExpo RouterDeep LinkingUniversal LinksApp LinksiOSAndroid
Compartir:XLinkedInWhatsApp

¿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.

Hablemos