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.

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.
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
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í:
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/privacyLos 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.
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.
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.
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:
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:
{
"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:
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.
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>
)
}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.
Navegación programática y la API del Router
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.
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.
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.
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:
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 />
}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 Original | Nueva Ubicación |
|---|---|
@react-navigation/native | expo-router/react-navigation |
@react-navigation/stack | expo-router/js-stack |
@react-navigation/bottom-tabs | expo-router/js-tabs |
Ejecutar el codemod para actualizar imports automáticamente:
npx expo-codemod sdk-56-expo-router-react-navigation-replace srcLa 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
- Documentación de Expo Router - guía oficial y referencia de API
- Changelog SDK Expo 57 - notas de la versión estable actual
- Post de blog Expo Router v56 - explicación del fork de React Navigation
- Guía de migración SDK 55 a 56 - pasos de migración de imports
Lo que los entrevistadores esperan sobre Expo Router
- Expo Router mapea automáticamente cada archivo en el directorio
appa una ruta, eliminando la configuración manual de navegación - Los layouts definidos a través de archivos
_layout.tsxcrean jerarquías de navegación para stacks, tabs y drawers - Las rutas dinámicas con
[param].tsxy rutas catch-all con[...slug].tsxmanejan 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 deexpo-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.
¿Sabrías detectar el bug en React Native?
Un fragmento real, un bug oculto, un intento al día. Sin cuenta para probar.

Escrito por
Anthony Fillion-MailletFundador 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
Compartir
Artículos relacionados

Desarrollo de Aplicaciones React Native en 2026: Guía Completa y Preguntas de Entrevista
Domina el desarrollo de aplicaciones React Native en 2026 con la Nueva Arquitectura, Hermes V1 y Fabric. Incluye ejemplos de código prácticos y preguntas de entrevista técnicas.

React Native 0.87 y SwiftPM en 2026: Build iOS Moderno y Preguntas de Entrevista
React Native 0.87 introduce soporte experimental para Swift Package Manager, eliminando CocoaPods de los builds iOS. Esta guía cubre la configuración de SwiftPM, la API TypeScript Estricta, optimizaciones de Metro 0.87, y preguntas de entrevista para desarrolladores móviles senior.

Hermes V1 en React Native 0.84: Rendimiento, Bytecode Precompilado y Preguntas de Entrevista
Análisis profundo de las optimizaciones de rendimiento de Hermes V1 en React Native 0.84: precompilación de bytecode, garbage collector Hades, gestión de memoria y preguntas técnicas esenciales para desarrolladores móviles.