Navegación en React Native 2026: Expo Router, React Navigation 7 y Deep Linking

Guía práctica de navegación en React Native en 2026. Domina Expo Router con enrutamiento basado en archivos, React Navigation 7 con su API estática, deep linking, Universal Links, rutas tipadas y optimización del rendimiento.

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:

  1. Comportamiento de navigate: En v7, navigate actúa como push por defecto — siempre añade una nueva copia de la pantalla al stack. Si quieres volver a una pantalla existente, ahora necesitas usar popTo.
  2. API estática: Es opcional pero recomendada para proyectos nuevos.
  3. 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:

  1. Instala Expo Router y configura el directorio app/
  2. Convierte cada Screen en un archivo dentro de app/
  3. Reemplaza navigation.navigate() por router.push()
  4. Migra NavigationContainer al layout raíz _layout.tsx
  5. 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 freezeOnBlur para liberar memoria en pantallas inactivas
  • Implementa useFocusEffect para operaciones de datos en lugar de useEffect
  • 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.

Sobre el Autor Editorial Team

Our team of expert writers and editors.