Introducción: ¿Cómo Está la Navegación en React Native en 2026?
Seamos honestos: la navegación puede hacer o deshacer tu app móvil. No importa cuán bonita sea la interfaz o cuán potente sea el backend — si el usuario no puede moverse con fluidez entre pantallas, la experiencia se cae. Y en el mundo de React Native, la buena noticia es que 2026 nos ha traído herramientas que hacen esto mucho más llevadero.
React Navigation 7 llegó con su API estática que simplifica bastante la configuración. Expo Router ya soporta (de forma experimental) React Server Components. Y React Navigation 8 viene en camino con pestañas nativas por defecto. Es un buen momento para ponerse al día.
En esta guía vamos a recorrer todo lo que necesitas saber: desde Expo Router y su enrutamiento basado en archivos, pasando por React Navigation 7, deep linking, Universal Links, rutas tipadas con TypeScript, hasta las mejores prácticas de rendimiento. Si estás arrancando un proyecto nuevo o migrando desde una versión anterior, aquí tienes todo cubierto.
Expo Router: Navegación Basada en Archivos para React Native
¿Qué es Expo Router y por qué deberías usarlo?
Expo Router es una biblioteca de enrutamiento basada en archivos que se construye sobre React Navigation. La idea es bastante sencilla: cada archivo que creas en el directorio app/ se convierte automáticamente en una ruta de tu aplicación. Si vienes del mundo web y has trabajado con Next.js, esto te va a resultar muy familiar.
Lo mejor es que elimina toneladas de boilerplate. Ya no tienes que configurar rutas manualmente.
Las ventajas principales incluyen:
- Enrutamiento automático: Crear un archivo equivale a crear una ruta
- Rutas tipadas: TypeScript genera tipos automáticamente para todas tus rutas
- Deep linking nativo: Todas las rutas son accesibles mediante deep links sin configuración extra
- Lazy bundling: En desarrollo, solo se carga el código necesario para la ruta actual
- Renderizado estático para web: Soporte para SEO y rendimiento web optimizado
- Soporte universal: La misma lógica funciona en iOS, Android y web
Configuración Inicial de Expo Router
Para crear un proyecto nuevo con Expo Router, solo necesitas estos comandos:
npx create-expo-app@latest mi-aplicacion
cd mi-aplicacion
npx expo start
Los proyectos creados con create-expo-app ya vienen con Expo Router configurado de fábrica. La estructura de archivos típica se ve así:
mi-aplicacion/
├── app/
│ ├── _layout.tsx // Layout raíz
│ ├── index.tsx // Ruta principal (/)
│ ├── about.tsx // Ruta /about
│ ├── (tabs)/ // Grupo de pestañas
│ │ ├── _layout.tsx // Layout de pestañas
│ │ ├── home.tsx // Pestaña /home
│ │ └── profile.tsx // Pestaña /profile
│ └── users/
│ ├── [id].tsx // Ruta dinámica /users/:id
│ └── index.tsx // Ruta /users
├── components/
├── constants/
└── package.json
Layouts y Navegación Anidada
Los archivos _layout.tsx son los que definen la estructura de navegación. Puedes anidar distintos tipos de navegadores para crear experiencias más complejas. Es aquí donde Expo Router realmente brilla, porque la jerarquía queda clara solo con mirar la estructura de carpetas.
// app/_layout.tsx
import { Stack } from 'expo-router';
export default function RootLayout() {
return (
<Stack>
<Stack.Screen
name="(tabs)"
options={{ headerShown: false }}
/>
<Stack.Screen
name="modal"
options={{ presentation: 'modal' }}
/>
</Stack>
);
}
// app/(tabs)/_layout.tsx
import { Tabs } from 'expo-router';
import { Ionicons } from '@expo/vector-icons';
export default function TabLayout() {
return (
<Tabs
screenOptions={{
tabBarActiveTintColor: '#007AFF',
headerStyle: { backgroundColor: '#f8f9fa' },
}}
>
<Tabs.Screen
name="home"
options={{
title: 'Inicio',
tabBarIcon: ({ color, size }) => (
<Ionicons name="home" size={size} color={color} />
),
}}
/>
<Tabs.Screen
name="profile"
options={{
title: 'Perfil',
tabBarIcon: ({ color, size }) => (
<Ionicons name="person" size={size} color={color} />
),
}}
/>
</Tabs>
);
}
Rutas Dinámicas y Parámetros
Las rutas dinámicas se crean usando corchetes en el nombre del archivo. Sí, es así de simple. Esto te permite capturar segmentos de la URL como parámetros:
// app/users/[id].tsx
import { useLocalSearchParams } from 'expo-router';
import { View, Text, StyleSheet } from 'react-native';
export default function UserDetail() {
const { id } = useLocalSearchParams<{ id: string }>();
return (
<View style={styles.container}>
<Text style={styles.title}>Usuario #{id}</Text>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
title: {
fontSize: 24,
fontWeight: 'bold',
},
});
Y si necesitas capturar múltiples segmentos, puedes usar la sintaxis catch-all con [...slug].tsx:
// app/blog/[...slug].tsx
import { useLocalSearchParams } from 'expo-router';
export default function BlogPost() {
const { slug } = useLocalSearchParams<{ slug: string[] }>();
// /blog/2026/enero/mi-articulo → slug = ['2026', 'enero', 'mi-articulo']
return (
<View>
<Text>Ruta: {slug?.join('/')}</Text>
</View>
);
}
Navegación Programática
Expo Router te da dos formas de navegar: el hook useRouter para hacerlo desde código, y el componente Link para navegación declarativa. Personalmente, termino usando ambos dependiendo del contexto.
import { useRouter, Link } from 'expo-router';
import { View, Text, Pressable, StyleSheet } from 'react-native';
export default function HomeScreen() {
const router = useRouter();
const handleNavigate = () => {
// Navegación programática
router.push('/users/42');
};
const handleReplace = () => {
// Reemplazar la pantalla actual (sin retroceso)
router.replace('/login');
};
const handleGoBack = () => {
// Retroceder a la pantalla anterior
router.back();
};
return (
<View style={styles.container}>
{/* Navegación declarativa con Link */}
<Link href="/about" style={styles.link}>
Ir a Acerca de
</Link>
{/* Navegación con parámetros */}
<Link
href={{
pathname: '/users/[id]',
params: { id: '42' },
}}
style={styles.link}
>
Ver Usuario 42
</Link>
<Pressable onPress={handleNavigate} style={styles.button}>
<Text style={styles.buttonText}>Navegar programáticamente</Text>
</Pressable>
</View>
);
}
Rutas Tipadas con TypeScript
Esto es probablemente una de las funcionalidades que más agradecerás en proyectos grandes. Las rutas tipadas de Expo Router te dan autocompletado y verificación de tipos para todas tus rutas. Adiós a los errores de tipeo en strings de navegación que solo descubres en runtime.
Cómo Habilitar las Rutas Tipadas
Solo necesitas añadir esta configuración en tu app.json:
{
"expo": {
"experiments": {
"typedRoutes": true
}
}
}
Cuando ejecutas npx expo start, el CLI genera automáticamente un archivo de tipos con todas las rutas disponibles. El resultado es que cualquier error de ruta se detecta en tiempo de compilación, no cuando el usuario toca un botón:
import { Link } from 'expo-router';
// ✅ Correcto - TypeScript verifica que la ruta existe
<Link href="/users/42">Ver Usuario</Link>
// ❌ Error de TypeScript - la ruta no existe
<Link href="/ruta-inexistente">Error</Link>
// ✅ Correcto - con parámetros tipados
<Link href={{ pathname: '/users/[id]', params: { id: '42' } }}>
Ver Usuario
</Link>
React Navigation 7: La API Estática
Si bien Expo Router es lo que recomendaría para proyectos Expo, React Navigation 7 sigue siendo fundamental cuando trabajas con configuraciones personalizadas o proyectos bare React Native. Y lo más interesante de la versión 7 es, sin duda, la API estática.
API Estática vs API Dinámica
La API estática te permite definir la configuración de navegación como un objeto JavaScript plano, en lugar de usar componentes JSX. Esto no solo simplifica la configuración, sino que mejora mucho la integración con deep linking:
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { createStaticNavigation } from '@react-navigation/native';
// API Estática (React Navigation 7)
const RootStack = createNativeStackNavigator({
initialRouteName: 'Home',
screenOptions: {
headerTintColor: '#ffffff',
headerStyle: {
backgroundColor: '#6200ee',
},
},
screens: {
Home: {
screen: HomeScreen,
options: {
title: 'Inicio',
},
linking: {
path: 'home',
},
},
Profile: {
screen: ProfileScreen,
linking: {
path: 'profile/:userId',
parse: {
userId: Number,
},
},
},
Settings: {
screen: SettingsScreen,
options: {
title: 'Configuración',
},
if: useIsLoggedIn, // Renderizado condicional
},
},
});
const Navigation = createStaticNavigation(RootStack);
export default function App() {
return <Navigation />;
}
¿Qué ganas con la API estática?
- Deep linking integrado: Se configura junto a cada pantalla, no en un objeto separado (esto por sí solo vale la pena)
- Renderizado condicional con
if: Puedes mostrar u ocultar pantallas basándote en hooks - Mejor inferencia de tipos: TypeScript puede inferir los tipos de parámetros directamente
- Menos boilerplate: No necesitas crear NavigationContainer ni definir tipos de parámetros manualmente
Flujo de Autenticación con la API Estática
Una de las cosas que más me gusta de la API estática es la propiedad if. Permite implementar flujos de autenticación de manera declarativa, sin toda la lógica condicional que solíamos escribir:
import { createNativeStackNavigator } from '@react-navigation/native-stack';
import { createStaticNavigation } from '@react-navigation/native';
function useIsSignedIn() {
const { user } = useAuth();
return user !== null;
}
function useIsSignedOut() {
const { user } = useAuth();
return user === null;
}
const RootStack = createNativeStackNavigator({
screens: {
// Pantallas solo para usuarios autenticados
Home: {
screen: HomeScreen,
if: useIsSignedIn,
},
Profile: {
screen: ProfileScreen,
if: useIsSignedIn,
},
// Pantallas solo para usuarios no autenticados
Login: {
screen: LoginScreen,
if: useIsSignedOut,
},
Register: {
screen: RegisterScreen,
if: useIsSignedOut,
},
},
});
const Navigation = createStaticNavigation(RootStack);
export default function App() {
return (
<AuthProvider>
<Navigation />
</AuthProvider>
);
}
Combinando API Estática y Dinámica
Lo bueno es que React Navigation 7 no te obliga a elegir. Puedes mezclar ambas APIs cuando necesites la flexibilidad de la API dinámica en ciertas partes de tu app:
import { createNativeStackNavigator } from '@react-navigation/native-stack';
// Definir un grupo dinámico dentro de una configuración estática
const HomeTabs = createBottomTabNavigator({
screens: {
Feed: FeedScreen,
Notifications: NotificationsScreen,
},
});
const RootStack = createNativeStackNavigator({
screens: {
HomeTabs: {
screen: HomeTabs,
options: { headerShown: false },
},
Details: DetailScreen,
},
});
Deep Linking y Universal Links
El deep linking es una de esas cosas que parece secundaria hasta que la necesitas. Permite que enlaces externos abran directamente la pantalla correcta de tu app — súper útil para compartir contenido, notificaciones push, y campañas de marketing.
Deep Linking Automático con Expo Router
Con Expo Router, el deep linking funciona automáticamente. Cada archivo de ruta genera un deep link correspondiente sin que tengas que configurar nada:
// Estructura de archivos → Deep links automáticos
app/index.tsx → miapp://
app/about.tsx → miapp://about
app/users/[id].tsx → miapp://users/42
app/(tabs)/home.tsx → miapp://home
Honestamente, esto es lo que me convenció de usar Expo Router en primer lugar. Cero configuración de deep linking.
Configuración de Universal Links (iOS) y App Links (Android)
Los Universal Links y App Links usan URLs HTTPS estándar que abren tu app si está instalada, o redirigen al sitio web si no lo está. Esta es la configuración que vas a querer para producción:
// app.json
{
"expo": {
"ios": {
"associatedDomains": [
"applinks:midominio.com"
]
},
"android": {
"intentFilters": [
{
"action": "VIEW",
"autoVerify": true,
"data": [
{
"scheme": "https",
"host": "midominio.com",
"pathPrefix": "/"
}
],
"category": ["BROWSABLE", "DEFAULT"]
}
]
}
}
}
Del lado del servidor, necesitas configurar los archivos de verificación. Sin esto, no funciona:
// Para iOS: /.well-known/apple-app-site-association
{
"applinks": {
"apps": [],
"details": [
{
"appID": "TEAM_ID.com.miapp.bundle",
"paths": ["*"]
}
]
}
}
// Para Android: /.well-known/assetlinks.json
[
{
"relation": ["delegate_permission/common.handle_all_urls"],
"target": {
"namespace": "android_app",
"package_name": "com.miapp.android",
"sha256_cert_fingerprints": ["TU_FINGERPRINT"]
}
}
]
Manejo de Deep Links Entrantes
Si necesitas lógica personalizada cuando tu app recibe un deep link, puedes usar el hook useURL de Expo Linking:
import * as Linking from 'expo-linking';
import { useEffect } from 'react';
import { useRouter } from 'expo-router';
export function useDeepLinkHandler() {
const router = useRouter();
useEffect(() => {
const subscription = Linking.addEventListener('url', ({ url }) => {
const parsed = Linking.parse(url);
// Lógica personalizada según la ruta
if (parsed.path?.startsWith('producto/')) {
const productoId = parsed.path.split('/')[1];
router.push(`/productos/${productoId}`);
}
});
return () => subscription.remove();
}, [router]);
}
Patrones Avanzados de Navegación
Grupos de Rutas y Layouts Compartidos
Los grupos de rutas (las carpetas con paréntesis en el nombre) te permiten organizar rutas sin afectar la URL final. Son especialmente útiles para separar el flujo de autenticación del resto de la app:
app/
├── (auth)/
│ ├── _layout.tsx // Layout sin header
│ ├── login.tsx // /login
│ └── register.tsx // /register
├── (app)/
│ ├── _layout.tsx // Layout con tabs
│ ├── (tabs)/
│ │ ├── _layout.tsx
│ │ ├── home.tsx // /home
│ │ └── search.tsx // /search
│ └── settings.tsx // /settings
└── _layout.tsx // Layout raíz
Y así es como se implementa el flujo de autenticación en los layouts:
// app/_layout.tsx - Gestión del flujo de autenticación
import { Stack, Redirect } from 'expo-router';
import { useAuth } from '../hooks/useAuth';
export default function RootLayout() {
const { isAuthenticated, isLoading } = useAuth();
if (isLoading) {
return <SplashScreen />;
}
return (
<Stack screenOptions={{ headerShown: false }}>
<Stack.Screen name="(auth)" />
<Stack.Screen name="(app)" />
</Stack>
);
}
// app/(app)/_layout.tsx - Proteger rutas autenticadas
import { Redirect } from 'expo-router';
import { useAuth } from '../../hooks/useAuth';
export default function AppLayout() {
const { isAuthenticated } = useAuth();
if (!isAuthenticated) {
return <Redirect href="/login" />;
}
return <Stack />;
}
Modales y Pantallas Flotantes
Los modales son parte esencial de cualquier app móvil. En Expo Router configurarlos es bastante directo:
// app/_layout.tsx
import { Stack } from 'expo-router';
export default function RootLayout() {
return (
<Stack>
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen
name="modal"
options={{
presentation: 'modal',
headerTitle: 'Nuevo Elemento',
headerLeft: () => null, // Ocultar botón de retroceso
}}
/>
<Stack.Screen
name="fullscreen-modal"
options={{
presentation: 'fullScreenModal',
animation: 'slide_from_bottom',
}}
/>
</Stack>
);
}
// app/modal.tsx
import { useRouter } from 'expo-router';
import { View, Text, Pressable, StyleSheet } from 'react-native';
export default function Modal() {
const router = useRouter();
return (
<View style={styles.container}>
<Text style={styles.title}>Contenido del Modal</Text>
<Pressable onPress={() => router.back()} style={styles.closeButton}>
<Text style={styles.closeText}>Cerrar</Text>
</Pressable>
</View>
);
}
Pantallas de Error y Rutas No Encontradas
No olvides manejar el caso en que alguien navega a una ruta que no existe. Expo Router lo hace fácil con el archivo especial +not-found.tsx:
// app/+not-found.tsx
import { Link, Stack } from 'expo-router';
import { View, Text, StyleSheet } from 'react-native';
export default function NotFoundScreen() {
return (
<>
<Stack.Screen options={{ title: 'Página no encontrada' }} />
<View style={styles.container}>
<Text style={styles.title}>404</Text>
<Text style={styles.subtitle}>
Esta pantalla no existe.
</Text>
<Link href="/" style={styles.link}>
Volver al inicio
</Link>
</View>
</>
);
}
React Server Components en Expo Router (Experimental)
Bueno, aquí viene lo emocionante. Una de las novedades más interesantes de 2026 es el soporte experimental para React Server Components (RSC) en Expo Router. Por primera vez puedes ejecutar componentes de React en el servidor y transmitir el resultado directamente a apps nativas.
¿Cómo Funcionan los RSC en React Native?
Los React Server Components te permiten ejecutar lógica en el servidor, acceder de forma segura a bases de datos y variables de entorno, y enviar únicamente el resultado renderizado al cliente. ¿El beneficio? Menos tamaño de bundle y mejores tiempos de carga.
// app/productos/[id].tsx - Server Component
// Este componente se ejecuta en el servidor
import { db } from '../../lib/database';
export default async function ProductoDetail({ params }) {
// Acceso seguro a la base de datos desde el servidor
const producto = await db.productos.findUnique({
where: { id: params.id },
});
return (
<View>
<Text style={{ fontSize: 24 }}>{producto.nombre}</Text>
<Text>{producto.descripcion}</Text>
<Text style={{ fontSize: 18, fontWeight: 'bold' }}>
${producto.precio}
</Text>
{/* Componente del cliente para interactividad */}
<AddToCartButton productoId={producto.id} />
</View>
);
}
Ojo: Los RSC en Expo Router todavía están en fase experimental. No los uses en producción todavía, y ten en cuenta que EAS Update aún no es compatible con Server Components. Pero sin duda representan hacia dónde va el futuro de las apps universales.
React Navigation 8: Lo que Viene
React Navigation 8 está actualmente en alfa, pero ya trae cambios significativos que vale la pena tener en el radar.
Pestañas Nativas por Defecto
El Bottom Tab Navigator ahora usa primitivas nativas por defecto en iOS y Android. Esto significa que obtienes una apariencia nativa sin configuración adicional. En iOS 26, incluso incluye el nuevo efecto liquid glass (que se ve bastante bien, por cierto):
import { createNativeBottomTabNavigator } from '@react-navigation/native-bottom-tabs';
// Las pestañas ahora usan componentes nativos por defecto
const Tabs = createNativeBottomTabNavigator({
screens: {
Home: {
screen: HomeScreen,
options: {
title: 'Inicio',
tabBarIcon: ({ color }) => (
<Icon name="home" color={color} />
),
},
},
Search: {
screen: SearchScreen,
options: {
title: 'Buscar',
tabBarIcon: ({ color }) => (
<Icon name="search" color={color} />
),
},
},
},
});
Mejoras en TypeScript
Los tipos de TypeScript se han rediseñado por completo para resolver esos problemas de inferencia que todos hemos sufrido. Ahora acceder a los parámetros de pantallas padre es mucho más intuitivo:
import { useRoute } from '@react-navigation/native';
function ChildComponent() {
// Nuevo: acceso a parámetros de pantalla padre por nombre
const route = useRoute('Profile');
const userId = route.params.userId;
return <Text>Usuario: {userId}</Text>;
}
Requisitos de la Nueva Arquitectura
Un detalle importante: React Navigation 8 requiere la Nueva Arquitectura de React Native. Ya no soporta la arquitectura legacy con el bridge. Necesitarás React Native 0.81 o superior con JSI, Fabric y TurboModules habilitados. Si todavía estás en la arquitectura antigua, es buen momento para planificar la migración.
Optimización del Rendimiento de la Navegación
La navegación puede convertirse fácilmente en un cuello de botella de rendimiento si no le prestas atención. Aquí van las técnicas que realmente marcan la diferencia.
Lazy Loading de Pantallas
import { lazy, Suspense } from 'react';
// Cargar pantallas de forma diferida
const HeavyScreen = lazy(() => import('./screens/HeavyScreen'));
// En tu configuración de navegación
<Stack.Screen name="heavy">
{() => (
<Suspense fallback={<LoadingSpinner />}>
<HeavyScreen />
</Suspense>
)}
</Stack.Screen>
Prevención de Re-renders Innecesarios
Este es probablemente el truco más importante: usa useFocusEffect en lugar de useEffect para operaciones de datos en pantallas. La diferencia es que solo se ejecuta cuando la pantalla está realmente visible.
import { useCallback, memo } from 'react';
import { useFocusEffect } from 'expo-router';
// Usar useFocusEffect en lugar de useEffect para operaciones de pantalla
function ProductListScreen() {
const [productos, setProductos] = useState([]);
useFocusEffect(
useCallback(() => {
// Solo se ejecuta cuando la pantalla está enfocada
fetchProductos().then(setProductos);
return () => {
// Limpieza al perder el foco
};
}, [])
);
return (
<FlatList
data={productos}
renderItem={({ item }) => <ProductCard producto={item} />}
keyExtractor={(item) => item.id}
/>
);
}
// Memoizar componentes de las pantallas
const ProductCard = memo(function ProductCard({ producto }) {
return (
<View style={styles.card}>
<Text>{producto.nombre}</Text>
<Text>${producto.precio}</Text>
</View>
);
});
Optimización de Transiciones y Animaciones
import { Stack } from 'expo-router';
// Configurar animaciones nativas para transiciones fluidas
export default function Layout() {
return (
<Stack
screenOptions={{
// Usar animaciones nativas (más performantes)
animation: 'slide_from_right',
// Habilitar pantallas nativas para mejor rendimiento de memoria
freezeOnBlur: true,
// Limitar el número de pantallas renderizadas
detachPreviousScreen: true,
}}
/>
);
}
Gestión del Estado de Navegación
Persistencia del Estado de Navegación
Guardar y restaurar el estado de navegación es uno de esos detalles que los usuarios agradecen mucho, aunque no siempre lo noten conscientemente. Cuando reabren la app y están justo donde la dejaron, eso genera confianza.
import AsyncStorage from '@react-native-async-storage/async-storage';
import { NavigationContainer } from '@react-navigation/native';
import { useEffect, useState, useCallback } from 'react';
const NAVIGATION_STATE_KEY = 'NAVIGATION_STATE';
function App() {
const [isReady, setIsReady] = useState(false);
const [initialState, setInitialState] = useState();
useEffect(() => {
const restoreState = async () => {
try {
const savedState = await AsyncStorage.getItem(
NAVIGATION_STATE_KEY
);
if (savedState) {
setInitialState(JSON.parse(savedState));
}
} finally {
setIsReady(true);
}
};
restoreState();
}, []);
const onStateChange = useCallback(async (state) => {
await AsyncStorage.setItem(
NAVIGATION_STATE_KEY,
JSON.stringify(state)
);
}, []);
if (!isReady) return <SplashScreen />;
return (
<NavigationContainer
initialState={initialState}
onStateChange={onStateChange}
>
{/* Tu navegación */}
</NavigationContainer>
);
}
Navegación con Estado Global
Integrar la navegación con una librería como Zustand te permite crear flujos reactivos que responden automáticamente a cambios de estado. Es un patrón que uso en prácticamente todos mis proyectos:
import { create } from 'zustand';
import { useRouter, useSegments } from 'expo-router';
import { useEffect } from 'react';
// Store de autenticación
const useAuthStore = create((set) => ({
user: null,
token: null,
login: async (credentials) => {
const response = await api.login(credentials);
set({ user: response.user, token: response.token });
},
logout: () => set({ user: null, token: null }),
}));
// Hook para proteger rutas
export function useProtectedRoute() {
const { user } = useAuthStore();
const segments = useSegments();
const router = useRouter();
useEffect(() => {
const inAuthGroup = segments[0] === '(auth)';
if (!user && !inAuthGroup) {
// Redirigir al login si no está autenticado
router.replace('/login');
} else if (user && inAuthGroup) {
// Redirigir al inicio si ya está autenticado
router.replace('/');
}
}, [user, segments]);
}
Migración y Mejores Prácticas
Migrar de React Navigation 6 a 7
Si estás migrando desde React Navigation 6, hay algunos cambios importantes que te pueden pillar desprevenido:
- Comportamiento de
navigate: En v7,navigateactúa comopushpor defecto — siempre añade una nueva copia de la pantalla al stack. Si quieres volver a una pantalla existente, ahora necesitas usarpopTo. - API estática: Es opcional pero recomendada para proyectos nuevos.
- Tipos de parámetros: Se recomienda migrar a la inferencia automática de tipos.
// React Navigation 6
navigation.navigate('Profile', { userId: '42' });
// Esto devolvería a Profile si ya existe en el stack
// React Navigation 7
navigation.navigate('Profile', { userId: '42' });
// Esto SIEMPRE añade una nueva instancia de Profile
// Para el comportamiento anterior, usar:
navigation.popTo('Profile', { userId: '42' });
Migrar de React Navigation a Expo Router
Si decides dar el salto a Expo Router, estos son los pasos principales:
- Instala Expo Router y configura el directorio
app/ - Convierte cada
Screenen un archivo dentro deapp/ - Reemplaza
navigation.navigate()porrouter.push() - Migra
NavigationContaineral layout raíz_layout.tsx - Actualiza los imports de hooks a sus equivalentes de
expo-router
No es un proceso instantáneo, pero si lo haces pantalla por pantalla es bastante manejable.
Checklist de Mejores Prácticas
- Usa
freezeOnBlurpara liberar memoria en pantallas inactivas - Implementa
useFocusEffectpara operaciones de datos en lugar deuseEffect - Habilita rutas tipadas para atrapar errores en tiempo de compilación
- Configura Universal Links para una experiencia de deep linking profesional
- Usa grupos de rutas para organizar flujos sin afectar las URLs
- Implementa pantallas de error y rutas no encontradas
- Persiste el estado de navegación para mejorar la experiencia del usuario
- Memoiza los componentes de pantallas para evitar re-renders innecesarios
Conclusión
La navegación en React Native ha madurado muchísimo en 2026. Expo Router se ha consolidado como la opción preferida para proyectos Expo, con su enrutamiento basado en archivos, deep linking automático y rutas tipadas. React Navigation 7, con su API estática, sigue siendo una excelente alternativa para proyectos bare React Native.
Mi consejo: si estás empezando un proyecto nuevo con Expo, ve directo a Expo Router. Si ya tienes un proyecto con React Navigation, la versión 7 tiene suficientes mejoras para justificar una actualización gradual.
Y de cara al futuro, tanto los React Server Components como React Navigation 8 apuntan a una convergencia cada vez mayor entre las capacidades web y nativas. React Native sigue demostrando que es una plataforma seria para el desarrollo multiplataforma, y las herramientas de navegación son una gran parte de esa historia.