Expo Router puede generar tipos de ruta estáticos desde tu árbol de archivos app/, dando a <Link>, router.push y los hooks de parámetros comprobaciones en tiempo de compilación. Los parámetros de ruta aún cruzan un límite de confianza - se originan desde URLs, notificaciones e intents del SO - así que empareja los tipos generados con validación en tiempo de ejecución.
// app/user/[id].tsximport { Link, router, useLocalSearchParams, type Href } from "expo-router";import { Text, View } from "react-native";import { z } from "zod";const Params = z.object({ id: z.string().regex(/^\d+$/),});export default function UserScreen() { const raw = useLocalSearchParams<"/user/[id]">(); const { id } = Params.parse(raw); // comprobación en tiempo de ejecución - params son cadenas return ( <View> <Text>Usuario {id}</Text> <Link href={{ pathname: "/user/[id]", params: { id: "42" } }}>Usuario 42</Link> </View> );}// Navegación imperativa - Href captura errores tipográficosexport function openUser(userId: string) { const href = { pathname: "/user/[id]", params: { id: userId } } satisfies Href; router.push(href);}
Cuándo usarlo: Usas rutas basadas en archivos de Expo Router y deseas autocompletar en href, segmentos de ruta tipificados en pantallas y validación antes de confiar en parámetros de enlace profundo.
Con experiments.typedRoutes: true, Expo CLI genera tipos cuando ejecutas npx expo start (o npx expo customize tsconfig.json en CI).
Las definiciones generadas aterrizan en .expo/types/router.d.ts y se extraen a través de expo-env.d.ts - no edites ni confirmes estos archivos.
Href acepta rutas estáticas ("/places") y formas HrefObject para segmentos dinámicos ({ pathname: "/places/[placeId]", params: { placeId } }).
useLocalSearchParams<Route>() tipifica nombres de segmentos de ruta para ese patrón de pantalla; las claves de consulta son principalmente manuales a menos que se declaren a través del segundo genérico.
Todos los valores de parámetros llegan como cadenas (o string[] para catch-alls y claves repetidas) - TypeScript no puede probar la coerción en tiempo de ejecución.
Las rutas relativas (./profile) no son parte de Href; construye rutas absolutas con useSegments() cuando necesites navegación relativa de pestañas.
import { type Href, useRouter, useSegments } from "expo-router";// Navegación de perfil relativa a la pestaña sin cadenas Href relativasfunction ProfileShortcut() { const [group] = useSegments(); const router = useRouter(); const href = `/${group}/profile` as Href; // convierte después de construir desde segmentos return null; // conecta a un Pressable en la interfaz de usuario real}// Navegación imperativa estrecha detrás de un auxiliarfunction navToPlace(placeId: string, tab: "overview" | "photos" = "overview") { const router = useRouter(); router.push({ pathname: "/places/[placeId]", params: { placeId, tab }, });}
Ruta de cadena para rutas dinámicas - <Link href="/places/[placeId]" /> puede pasar la verificación de tipos en configuraciones sueltas pero omite params requerido. Solución: Usa objetos { pathname, params } para cada segmento [param].
Confiar en parámetros tipificados como números - useLocalSearchParams<"/product/[id]">() aún devuelve id: string. Solución: Analiza con z.coerce.number() o Number() detrás de la validación.
Tipos obsoletos después de mover archivos - Renombrar rutas sin reiniciar Metro deja uniones Href obsoletas. Solución: Reinicia npx expo start o ejecuta el comando de personalización en CI antes de tsc.
Parámetros de consulta asumidos como auto-tipificados - Solo los segmentos del sistema de archivos aparecen en tipos generados. Solución: Agrega un segundo genérico a useLocalSearchParams o un esquema Zod para claves ?tab= y ?ref=.
Sorpresas de tipo catch-all - [...terms] puede ser string cuando hay un segmento y string[] cuando hay varios. Solución: Normaliza con Array.isArray(v) ? v : [v] antes de usar.
Cadenas href relativas - ./settings se excluye intencionalmente de Href. Solución: Prefijo con useSegments() o rutas absolutas codificadas.
Parámetros opcionales en push - Pasar params: { id: undefined } puede convertir "undefined" en la URL. Solución: Omite claves o filtra antes de la navegación.
Establece experiments.typedRoutes en true en app.json, ejecuta npx expo customize tsconfig.json, luego inicia el servidor dev con npx expo start. Los tipos generados se actualizan cuando cambian los archivos de ruta.
¿Dónde se almacenan los tipos de ruta generados?
Expo CLI escribe en .expo/types/ y los referencia desde un expo-env.d.ts ignorado por git en la raíz del proyecto. No confirmes ni edites manualmente estos archivos.
¿Por qué las rutas dinámicas deben usar objetos href?
Los segmentos dinámicos necesitan un mapa params junto con pathname. La forma de objeto asegura que cada token [placeId] o [id] reciba un valor y coincida con la unión Href generada.
O valida con Zod para enums, valores por defecto y coerción.
¿Cuál es la diferencia entre useLocalSearchParams y useGlobalSearchParams?
useLocalSearchParams devuelve parámetros para el patrón de ruta de la pantalla actual. useGlobalSearchParams devuelve los parámetros fusionados para todo el árbol de navegación - útil para rutas catch-all y layouts anidados, pero más amplio en alcance.
¿Se tipifcan los parámetros de ruta como cadenas?
Sí. Los segmentos de URL y los valores de consulta son cadenas en el límite nativo. Los genéricos de TypeScript nombran las claves; Zod (o similar) prueba las formas de valor en tiempo de ejecución.
¿Cómo navego imperativamente con tipos?
Importa router desde expo-router o llama useRouter(). Ambos aceptan rutas Href: router.push("/places") para rutas estáticas y router.push({ pathname: "/places/[placeId]", params: { placeId } }) para dinámicas.
¿Puedo usar rutas tipificadas sin iniciar Metro?
Ejecuta npx expo customize tsconfig.json en CI antes de tsc para que los tipos existan sin un servidor dev. Para archivos de ruta nuevos, aún regenera cuando el árbol app/ cambia.
Trampa: ¿Por qué el autocompletar no muestra mi ruta nueva?
Los tipos se generan al iniciar el servidor dev. Si agregaste un archivo mientras Metro estaba inactivo, reinicia npx expo start. Confirma que experiments.typedRoutes es true y que tsconfig.json incluye expo-env.d.ts.
¿Cómo tipifcan sus parámetros las rutas catch-all?
Un archivo search/[...terms].tsx se asigna a useLocalSearchParams<"/search/[...terms]">() con terms como string | string[]. Normaliza a un array antes de unir o mostrar.
¿Debo validar parámetros si TypeScript ya los comprueba?
Sí. Las rutas tipificadas prueban la forma de la ruta, no la entrada del usuario. Los enlaces profundos, códigos QR e intents compartidos por el SO pueden suministrar valores malformados - valida en el límite de pantalla.
¿Cómo afectan los grupos de ruta a los tipos?
Los grupos como (tabs) se omiten de la URL pero aparecen en rutas de archivo. Los tipos generados incluyen segmentos de grupo donde sea aplicable - usa el literal de cadena exacto que TypeScript sugiere al pasar genéricos a hooks.
¿Cómo construyo enlaces relativos a la pestaña sin href relativo?
Lee los segmentos actuales con useSegments() y construye una ruta absoluta:
const [group] = useSegments();<Link href={`/${group}/profile` as Href}>Perfil</Link>
¿Pueden los esquemas de Zod compartirse entre API y parámetros de ruta?
Sí. Reutiliza primitivos (z.string().uuid()) en esquemas de respuesta API y esquemas de parámetros de ruta. Mantén objetos específicos de pantalla separados para evitar acoplar formas de fetch a formas de URL.
¿Qué pasa si migro desde listas de parámetros de React Navigation?
Mapea cada pantalla a una ruta de archivo, habilita rutas tipificadas, luego reemplaza incrementalmente las entradas manuales RootStackParamList. Ver Tipificación Gradual en Apps Brownfield para el ritmo de migración.