Expo Router en React Native: Guía Completa de Navegación Basada en Archivos

Dominar Expo Router para React Native con esta guía que cubre enrutamiento basado en archivos, layouts, rutas dinámicas, navegación tipada y patrones avanzados como modales y tabs.

Sistema de navegacion basado en archivos Expo Router para aplicaciones moviles React Native

Expo Router lleva el enrutamiento basado en archivos a React Native, reemplazando la configuración manual de navegación con un enfoque basado en convenciones inspirado en Next.js. A partir del SDK Expo 57 y Expo Router, construir navegación multiplataforma para Android, iOS y web requiere únicamente crear archivos en el directorio correcto.

Inicio Rápido

Los proyectos nuevos de Expo incluyen Expo Router preconfigurado. Ejecutar npx create-expo-app@latest --template default genera un proyecto con enrutamiento basado en archivos listo para usar. Los proyectos existentes pueden agregarlo instalando expo-router y actualizando el punto de entrada.

Cómo funciona el enrutamiento basado en archivos en Expo Router

Cada archivo dentro del directorio app se convierte automáticamente en una ruta. La ruta del archivo se mapea directamente a la ruta de la URL, eliminando la necesidad de una configuración de navegación centralizada. Un archivo en app/settings.tsx crea una ruta /settings, mientras que app/profile/edit.tsx se mapea a /profile/edit.

Este enfoque ofrece tres ventajas clave sobre la configuración tradicional de React Navigation:

  • Cero configuración: las rutas existen en el momento en que se crea el archivo
  • Deep linking automático: cada pantalla obtiene una URL, habilitando compartir y probar
  • Navegación tipada: TypeScript conoce qué rutas existen en tiempo de compilación
app/index.tsxtypescript
import { View, Text, StyleSheet } from 'react-native'
import { Link } from 'expo-router'

export default function HomeScreen() {
  return (
    <View style={styles.container}>
      <Text style={styles.title}>Welcome</Text>
      {/* Link maps directly to file path */}
      <Link href="/settings" style={styles.link}>
        Open Settings
      </Link>
      <Link href="/profile/edit" style={styles.link}>
        Edit Profile
      </Link>
    </View>
  )
}

const styles = StyleSheet.create({
  container: { flex: 1, justifyContent: 'center', padding: 24 },
  title: { fontSize: 28, fontWeight: 'bold', marginBottom: 16 },
  link: { fontSize: 16, color: '#61DAFB', marginTop: 12 },
})

El componente Link maneja la navegación en todas las plataformas. En la web, renderiza una etiqueta ancla con atributos href apropiados para SEO. En plataformas nativas, activa la navegación basada en stack.

Estructura del proyecto y archivos de layout

Expo Router utiliza archivos _layout.tsx para definir contenedores de navegación. Cada directorio puede tener su propio layout, creando jerarquías de navegación anidadas. El layout raíz envuelve toda la aplicación, mientras que los layouts anidados controlan secciones específicas.

Una estructura de proyecto típica luce así:

text
app/
  _layout.tsx          # Root layout (Stack or custom)
  index.tsx            # Home screen (/)
  (tabs)/              # Tab group (parentheses = route group)
    _layout.tsx        # Tab navigator
    home.tsx           # /home tab
    search.tsx         # /search tab
    profile.tsx        # /profile tab
  settings/
    _layout.tsx        # Settings stack layout
    index.tsx          # /settings
    notifications.tsx  # /settings/notifications
    privacy.tsx        # /settings/privacy

Los grupos de rutas, directorios envueltos en paréntesis, organizan archivos sin afectar la URL. El directorio (tabs) de arriba crea un navegador de pestañas, pero las URLs permanecen como /home, /search y /profile en lugar de /tabs/home.

app/_layout.tsxtypescript
import { Stack } from 'expo-router'

export default function RootLayout() {
  return (
    <Stack
      screenOptions={{
        headerStyle: { backgroundColor: '#1a1a2e' },
        headerTintColor: '#ffffff',
        headerTitleStyle: { fontWeight: '600' },
      }}
    >
      <Stack.Screen name="index" options={{ title: 'Home' }} />
      <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
      <Stack.Screen name="settings" options={{ title: 'Settings' }} />
    </Stack>
  )
}

El layout raíz también sirve como el lugar para cargar fuentes, inicializar providers y configurar ajustes globales.

Construyendo navegación por pestañas con Expo Router

La navegación por pestañas requiere un archivo _layout.tsx dentro de un grupo de rutas. Expo Router proporciona tanto NativeTabs para experiencias de pestañas específicas por plataforma como el componente estándar Tabs para la mayoría de los casos de uso. Desde el SDK 56, Native Tabs soporta la propiedad disabled para prevenir la selección de pestañas mientras se mantienen visibles.

app/(tabs)/_layout.tsxtypescript
import { Tabs } from 'expo-router'
import { Ionicons } from '@expo/vector-icons'

export default function TabLayout() {
  return (
    <Tabs
      screenOptions={{
        tabBarActiveTintColor: '#61DAFB',
        tabBarInactiveTintColor: '#888',
        tabBarStyle: {
          backgroundColor: '#1a1a2e',
          borderTopColor: '#2d2d44',
        },
      }}
    >
      <Tabs.Screen
        name="home"
        options={{
          title: 'Home',
          tabBarIcon: ({ color, size }) => (
            <Ionicons name="home" size={size} color={color} />
          ),
        }}
      />
      <Tabs.Screen
        name="search"
        options={{
          title: 'Search',
          tabBarIcon: ({ color, size }) => (
            <Ionicons name="search" size={size} color={color} />
          ),
        }}
      />
      <Tabs.Screen
        name="profile"
        options={{
          title: 'Profile',
          tabBarIcon: ({ color, size }) => (
            <Ionicons name="person" size={size} color={color} />
          ),
        }}
      />
    </Tabs>
  )
}

Cada archivo de pantalla de pestaña exporta un componente React estándar. El ícono, etiqueta y badge de la barra de pestañas se configuran a través de la prop options en el layout.

Rutas dinámicas y parámetros de ruta

Los segmentos dinámicos usan corchetes en el nombre del archivo. Un archivo llamado [id].tsx coincide con cualquier segmento único, mientras que [...slug].tsx captura todos los segmentos restantes.

app/product/[id].tsxtypescript
import { View, Text, StyleSheet } from 'react-native'
import { useLocalSearchParams, Stack } from 'expo-router'

export default function ProductScreen() {
  // Extract the dynamic parameter from the URL
  const { id } = useLocalSearchParams<{ id: string }>()

  return (
    <View style={styles.container}>
      <Stack.Screen options={{ title: `Product ${id}` }} />
      <Text style={styles.heading}>Product Details</Text>
      <Text style={styles.id}>ID: {id}</Text>
    </View>
  )
}

const styles = StyleSheet.create({
  container: { flex: 1, padding: 24 },
  heading: { fontSize: 24, fontWeight: 'bold', marginBottom: 8 },
  id: { fontSize: 16, color: '#888' },
})

Navegar a /product/42 renderiza esta pantalla con id establecido en "42". El hook useLocalSearchParams proporciona acceso tipado a todos los parámetros de ruta.

Para rutas catch-all, [...slug].tsx captura segmentos de ruta completos:

app/docs/[...slug].tsxtypescript
import { useLocalSearchParams } from 'expo-router'

export default function DocsScreen() {
  // /docs/getting-started/installation → slug = ['getting-started', 'installation']
  const { slug } = useLocalSearchParams<{ slug: string[] }>()

  return <DocViewer path={slug.join('/')} />
}

¿Listo para aprobar tus entrevistas de React Native?

Practica con nuestros simuladores interactivos, flashcards y tests técnicos.

Rutas tipadas para seguridad en tiempo de compilación

Expo Router genera tipos de rutas automáticamente cuando typed routes está habilitado. Esto detecta enlaces rotos en tiempo de compilación en lugar de en tiempo de ejecución.

Habilitar rutas tipadas en app.json:

json
{
  "expo": {
    "experiments": {
      "typedRoutes": true
    }
  }
}

Una vez habilitado, la prop href en Link y el argumento de router.push() solo aceptan strings de rutas válidas:

app/checkout.tsxtypescript
import { router } from 'expo-router'

function handleCheckout(cartId: string) {
  // TypeScript validates this route exists
  router.push(`/product/${cartId}`)

  // This would cause a compile error if /nonexistent doesn't exist
  // router.push('/nonexistent')
}

Las rutas tipadas funcionan bien con useLocalSearchParams. Los tipos generados aseguran que los nombres de parámetros coincidan entre la definición de la ruta y el componente que la consume, previniendo bugs sutiles que solo aparecen al navegar a una pantalla específica.

Pantallas modales y opciones de presentación

Los modales en Expo Router son pantallas regulares configuradas con presentation: 'modal' en el layout. Este enfoque mantiene la convención basada en archivos intacta: un modal es simplemente otra ruta.

app/_layout.tsxtypescript
import { Stack } from 'expo-router'

export default function RootLayout() {
  return (
    <Stack>
      <Stack.Screen name="index" />
      <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
      {/* Modal screen slides up from the bottom */}
      <Stack.Screen
        name="create-post"
        options={{
          presentation: 'modal',
          headerTitle: 'New Post',
        }}
      />
    </Stack>
  )
}
app/create-post.tsxtypescript
import { View, TextInput, Button, StyleSheet } from 'react-native'
import { router } from 'expo-router'
import { useState } from 'react'

export default function CreatePostModal() {
  const [title, setTitle] = useState('')

  const handleSubmit = () => {
    // Submit logic here
    router.back() // Dismiss the modal
  }

  return (
    <View style={styles.container}>
      <TextInput
        style={styles.input}
        placeholder="Post title"
        value={title}
        onChangeText={setTitle}
      />
      <Button title="Publish" onPress={handleSubmit} />
    </View>
  )
}

const styles = StyleSheet.create({
  container: { flex: 1, padding: 24 },
  input: {
    borderWidth: 1,
    borderColor: '#333',
    borderRadius: 8,
    padding: 12,
    fontSize: 16,
    marginBottom: 16,
  },
})

Navegar a /create-post activa la presentación modal. La llamada a router.back() la cierra, regresando a la pantalla anterior en el stack.

Más allá del componente Link, Expo Router proporciona una API imperativa a través del objeto router. Esto maneja la navegación activada por lógica de negocio en lugar de interacciones del usuario.

typescript
import { router } from 'expo-router'

// Push a new screen onto the stack
router.push('/profile/settings')

// Replace the current screen (no back button)
router.replace('/login')

// Go back to the previous screen
router.back()

// Navigate with parameters
router.push({
  pathname: '/product/[id]',
  params: { id: '42', source: 'recommendations' },
})

// Check if going back is possible
import { useRouter } from 'expo-router'

function BackButton() {
  const router = useRouter()
  return router.canGoBack() ? (
    <Button title="Back" onPress={() => router.back()} />
  ) : null
}

La distinción entre router.push y router.replace importa para los flujos de autenticación en React Native. Después de un inicio de sesión exitoso, router.replace('/dashboard') previene que el usuario navegue de regreso a la pantalla de login.

Navegación vs. Redirección

router.replace() reemplaza la entrada actual en el historial de navegación. Para guardas de autenticación que redirigen usuarios no autenticados, usar <Redirect href="/login" /> dentro del render del componente: se activa durante la fase de render y funciona correctamente con server-side rendering en web.

Middleware y protección de rutas

Expo Router proporciona middleware de servidor para lógica a nivel de ruta. El archivo +middleware.ts intercepta solicitudes antes de que lleguen al componente de la ruta, habilitando verificaciones de autenticación, redirecciones y manipulación de headers.

app/+middleware.tstypescript
import { type MiddlewareRequest } from 'expo-router/server'

export function middleware(request: MiddlewareRequest) {
  const { pathname } = request.nextUrl

  // Protect dashboard routes
  const protectedPaths = ['/dashboard', '/settings', '/profile']
  const isProtected = protectedPaths.some(p => pathname.startsWith(p))

  if (isProtected) {
    const token = request.cookies.get('session')
    if (!token) {
      return Response.redirect(new URL('/login', request.url))
    }
  }

  return undefined // Continue to route
}

En plataformas nativas donde el middleware de servidor no se ejecuta, la protección de rutas depende de guardas del lado del cliente. Un patrón común envuelve layouts protegidos con una verificación de autenticación:

app/(authenticated)/_layout.tsxtypescript
import { Redirect, Stack } from 'expo-router'
import { useAuth } from '@/hooks/useAuth'

export default function AuthenticatedLayout() {
  const { isLoggedIn, isLoading } = useAuth()

  if (isLoading) return null
  if (!isLoggedIn) return <Redirect href="/login" />

  return <Stack />
}
Consideraciones de Plataforma

El middleware de servidor solo se ejecuta en web con server-side rendering. Para aplicaciones nativas, siempre implementar guardas de ruta del lado del cliente en componentes de layout. Combinar ambos enfoques asegura protección consistente en todas las plataformas.

Migración SDK 56: Fork de React Navigation

A partir del SDK Expo 56, Expo Router ya no depende de los paquetes @react-navigation/*. Si el proyecto importa directamente desde React Navigation, actualizar los imports:

Paquete OriginalNueva Ubicación
@react-navigation/nativeexpo-router/react-navigation
@react-navigation/stackexpo-router/js-stack
@react-navigation/bottom-tabsexpo-router/js-tabs

Ejecutar el codemod para actualizar imports automáticamente:

bash
npx expo-codemod sdk-56-expo-router-react-navigation-replace src

La API en tiempo de ejecución permanece sin cambios. Solo los especificadores de módulos cambiaron. Las bibliotecas de terceros que aún importan desde @react-navigation/core funcionan automáticamente porque Expo CLI reescribe esos imports durante el bundling.

Fuentes

Lo que los entrevistadores esperan sobre Expo Router

  • Expo Router mapea automáticamente cada archivo en el directorio app a una ruta, eliminando la configuración manual de navegación
  • Los layouts definidos a través de archivos _layout.tsx crean jerarquías de navegación para stacks, tabs y drawers
  • Las rutas dinámicas con [param].tsx y rutas catch-all con [...slug].tsx manejan navegación parametrizada con soporte completo de TypeScript
  • Las rutas tipadas detectan enlaces de navegación rotos en tiempo de compilación cuando se habilitan en app.json
  • Las pantallas modales, navegación programática vía router.push/replace/back, y middleware de servidor completan el toolkit de enrutamiento
  • Los grupos de rutas usando directorios con paréntesis organizan código sin afectar URLs
  • Los guardas del lado del cliente en layouts manejan autenticación en nativo, mientras que el middleware de servidor cubre web
  • Desde el SDK 56, los imports desde @react-navigation/* deben actualizarse a equivalentes de expo-router/*

Para más sobre arquitectura de React Native y preparación para entrevistas, consultar el módulo de módulos nativos.

¡Empieza a practicar!

Pon a prueba tu conocimiento con nuestros simuladores de entrevista y tests técnicos.

Reto diario

¿Sabrías detectar el bug en React Native?

Un fragmento real, un bug oculto, un intento al día. Sin cuenta para probar.

Anthony Fillion-Maillet

Escrito por

Anthony Fillion-Maillet

Fundador de SharpSkill

Desarrollador fullstack desde hace más de 10 años. Dirige SharpSkill y responde por todo lo que se publica aquí.

Actualizado el 6 de octubre de 2026

Etiquetas

#react-native
#expo
#expo-router
#navigation
#mobile-development
#tutorial

Compartir

Artículos relacionados