← Regresar a lecciones

Navegación avanzada en React Native: rutas anidadas y headers

Rutas anidadas: organiza la complejidad

Cuando ya dominas los conceptos básicos de navegación en React Native, el siguiente paso natural es enfrentar escenarios más complejos; flujos de autenticación, rutas profundamente anidadas, headers que cambian según el contexto, y pantallas que aparecen o desaparecen según el estado de tu aplicación.

Estos patrones son los que separan una app funcional de una app profesional. Y aunque pueden parecer intimidantes al principio, una vez que comprendes la lógica detrás de cada uno, se vuelven herramientas poderosas para construir experiencias sofisticadas.

Rutas anidadas: organiza la complejidad

En aplicaciones reales, la navegación rara vez es plana. Imagina una app de comercio electrónico, tienes pestañas principales (inicio, carrito, perfil), pero dentro de "inicio" necesitas un stack para navegar entre categorías, productos y detalles. Dentro de "perfil", otro stack para editar datos, ver historial, gestionar direcciones.

Esto es navegación anidada; un tipo de navegación dentro de otro.

📁 Estructura típica

1app/ 2 ├─ (tabs)/ 3 │ ├─ _layout.tsx → Tab Navigator 4 │ ├─ index.tsx → Pestaña "Inicio" 5 │ ├─ cart.tsx → Pestaña "Carrito" 6 │ └─ profile/ 7 │ ├─ _layout.tsx → Stack Navigator interno 8 │ ├─ index.tsx → Pantalla principal del perfil 9 │ ├─ edit.tsx → Editar perfil 10 │ └─ history.tsx → Historial de pedidos 11 └─ _layout.tsx → Root layout

En este caso, el tab "Perfil" no es solo una pantalla: es un stack completo. Cuando el usuario entra a su perfil, puede navegar hacia editar datos o ver su historial, y al volver atrás, regresa a la pantalla principal del perfil, no a la pestaña anterior.

Implementación práctica

Tab Navigator principal:

1// app/(tabs)/_layout.tsx 2import { Tabs } from "expo-router"; 3import { Home, ShoppingCart, User } from "lucide-react-native"; 4 5export default function TabsLayout() { 6 return ( 7 <Tabs> 8 <Tabs.Screen 9 name="index" 10 options={{ 11 title: "Inicio", 12 tabBarIcon: ({ color }) => <Home color={color} size={24} /> 13 }} 14 /> 15 <Tabs.Screen 16 name="cart" 17 options={{ 18 title: "Carrito", 19 tabBarIcon: ({ color }) => <ShoppingCart color={color} size={24} /> 20 }} 21 /> 22 <Tabs.Screen 23 name="profile" 24 options={{ 25 title: "Perfil", 26 tabBarIcon: ({ color }) => <User color={color} size={24} /> 27 }} 28 /> 29 </Tabs> 30 ); 31}

Stack interno del perfil:

1// app/(tabs)/profile/_layout.tsx 2import { Stack } from "expo-router"; 3 4export default function ProfileLayout() { 5 return ( 6 <Stack> 7 <Stack.Screen 8 name="index" 9 options={{ headerShown: false }} // El tab ya muestra header 10 /> 11 <Stack.Screen 12 name="edit" 13 options={{ title: "Editar perfil" }} 14 /> 15 <Stack.Screen 16 name="history" 17 options={{ title: "Historial de pedidos" }} 18 /> 19 </Stack> 20 ); 21}

Ahora, desde la pantalla principal del perfil, puedes navegar así:

1// app/(tabs)/profile/index.tsx 2import { Link } from "expo-router"; 3 4export default function ProfileScreen() { 5 return ( 6 <View> 7 <Text>Bienvenido a tu perfil</Text> 8 <Link href="/profile/edit">Editar datos</Link> 9 <Link href="/profile/history">Ver historial</Link> 10 </View> 11 ); 12}

La navegación es relativa al contexto actual. No necesitas escribir rutas absolutas complejas: Expo Router entiende la jerarquía.

Headers personalizados: más que un título

El header de una pantalla no es solo estético: comunica contexto, ofrece acciones rápidas y refuerza la identidad de la app. Por defecto, Expo Router te da un header básico, pero las apps profesionales necesitan más control.

Personalización básica

Puedes modificar colores, fuentes y elementos del header desde las opciones de cada pantalla:

1<Stack.Screen 2 name="details" 3 options={{ 4 title: "Detalle del producto", 5 headerStyle: { backgroundColor: "#6200ee" }, 6 headerTintColor: "#fff", 7 headerTitleStyle: { fontWeight: "bold" } 8 }} 9/>

Esto funciona para casos simples. Pero, ¿qué pasa cuando necesitas un header completamente personalizado?

Header dinámico con componente custom

1<Stack.Screen 2 name="product" 3 options={{ 4 headerTitle: () => <CustomHeader />, 5 headerRight: () => ( 6 <TouchableOpacity onPress={() => console.log("Compartir")}> 7 <Share2 color="#fff" size={24} /> 8 </TouchableOpacity> 9 ) 10 }} 11/>

Puedes insertar cualquier componente React en el header. Esto abre posibilidades infinitas: buscadores en el header, botones de filtro, indicadores de carga, animaciones.

Headers que cambian según el scroll

Un patrón muy común en apps modernas es el header que se oculta al hacer scroll hacia abajo y reaparece al subir. Esto maximiza el espacio de contenido sin sacrificar la navegación.

Con React Navigation (la base de Expo Router), puedes lograrlo configurando opciones avanzadas:

1<Stack.Screen 2 name="feed" 3 options={{ 4 headerTransparent: true, 5 headerBlurEffect: "light" 6 }} 7/>

O mejor aún, usar Animated para crear transiciones suaves basadas en el scroll del usuario. Aunque esto requiere un poco más de código, el resultado es una experiencia fluida y profesional.

Deep linking: navega desde fuera de la app

El deep linking permite que usuarios abran tu app en una pantalla específica desde un enlace externo: un email, una notificación push, un código QR. Es fundamental para marketing, retención y experiencia de usuario.

Con Expo Router, el deep linking funciona automáticamente gracias a la estructura basada en archivos. Solo necesitas configurar el esquema de URL:

1// app.json 2{ 3 "expo": { 4 "scheme": "myapp" 5 } 6}

Ahora, un enlace como myapp://profile/42 abrirá la pantalla de perfil con userId: 42.

Para URLs reales (https://example.com/profile/42), necesitas configurar universal links y app links. Esto requiere archivos de verificación en tu dominio, pero Expo simplifica el proceso con EAS:

1{ 2 "expo": { 3 "ios": { 4 "associatedDomains": ["applinks:example.com"] 5 }, 6 "android": { 7 "intentFilters": [ 8 { 9 "action": "VIEW", 10 "data": { "scheme": "https", "host": "example.com" } 11 } 12 ] 13 } 14 } 15}

Ahora, cuando alguien haga clic en un enlace de tu dominio, el sistema operativo preguntará si quiere abrir la app. Si el usuario acepta, la app se abre directamente en la pantalla correcta.

Animaciones de transición personalizadas

Por defecto, React Navigation maneja las transiciones entre pantallas: deslizamiento horizontal en iOS, fade en Android. Pero puedes personalizar estas animaciones para crear experiencias únicas.

1<Stack.Screen 2 name="details" 3 options={{ 4 animation: "fade", 5 // o "slide_from_bottom", "slide_from_right", "flip", etc. 6 }} 7/>

Para animaciones más complejas, puedes usar @react-navigation/stack con gesture handlers y reanimated:

1cardStyleInterpolator: ({ current, layouts }) => ({ 2 cardStyle: { 3 transform: [ 4 { 5 translateX: current.progress.interpolate({ 6 inputRange: [0, 1], 7 outputRange: [layouts.screen.width, 0], 8 }), 9 }, 10 ], 11 }, 12})

Esto requiere entender interpolación y animaciones, pero el resultado es control total sobre cómo se mueven las pantallas. Algunas apps premium usan transiciones customizadas para reforzar su identidad visual.

Conclusión

La navegación avanzada no es magia; es arquitectura bien pensada. Rutas anidadas, headers dinámicos, flujos condicionales... cada técnica responde a un problema real que enfrentarás en aplicaciones de producción. Dominar estos patrones te permite construir apps que no solo funcionan, sino que se sienten profesionales.