Rutas con expo-router
El enrutado es por ficheros: cada .tsx de src/app/ es una ruta, y su ruta es su camino en
disco. No hay fichero de rutas que mantener.
app/
_layout.tsx envoltorio de TODA la app
index.tsx / bienvenida
login.tsx /login
mfa-challenge.tsx /mfa-challenge
forgot-password.tsx /forgot-password
(home)/
_layout.tsx envoltorio del área privada
index.tsx /(home) pantalla principal
settings.tsx /settings
pet/[id].tsx /pet/:id
El grupo entre paréntesis
(home) es un grupo: agrupa pantallas para darles un layout y unas reglas comunes sin
aparecer en la URL. settings.tsx dentro de (home) se navega como /settings, no como
/home/settings.
Aquí el grupo separa lo público de lo privado, y eso es toda la autorización de la app.
Proteger un área es tres líneas
En el _layout.tsx del grupo:
const { accessToken } = useAuth();
if (!accessToken) {
return <Redirect href="/login" />;
}
return <Stack screenOptions={{ headerShown: false, animation: 'slide_from_right' }} />;
No hay guard por pantalla ni comprobación repetida: entrar en el grupo es la comprobación. Una
pantalla nueva se protege con solo crearla dentro de (home).
El camino inverso también está cubierto: la pantalla de bienvenida redirige a /(home) si ya hay
sesión, así que un usuario autenticado no ve el login.
Al añadir una pantalla, lo primero que hay que decidir es si va dentro o fuera de
(home). Eso decide si requiere sesión.
Los dos layouts, y qué va en cada uno
app/_layout.tsx — envuelve todo. Aquí van los proveedores y la carga inicial: el
AuthProvider, GestureHandlerRootView, las fuentes, el i18n y el control del splash. Se pinta
null hasta que todo está listo.
app/(home)/_layout.tsx — solo el área privada. Aquí va lo que requiere sesión y debe
ocurrir una sola vez: registrar el token de push y suscribirse a los toques en las notificaciones,
los dos dependientes de accessToken.
Lo que necesita sesión va en el layout del grupo, no en el raíz. En el raíz se ejecutaría también para un usuario sin autenticar.
Navegar
const router = useRouter();
router.push('/login');
// con parámetros, en forma de objeto — no concatenando cadenas
router.push({ pathname: '/pet/[id]', params: { id: petId, tab: 'historial' } });
Y al recibirlos, useLocalSearchParams(). Los parámetros llegan siempre como cadenas, así que
un número o un booleano hay que convertirlos.
Con typedRoutes activado en app.json, una ruta mal escrita es un error de compilación y no un
descubrimiento en ejecución.
Cuándo push y cuándo replace
pushapila: el botón de volver regresa a la pantalla anterior. Es el caso normal.replacesustituye: para cuando volver atrás no tiene sentido, como después de un login.<Redirect>es declarativo, para decidirlo durante el render —la protección de arriba— en lugar de dentro de un efecto.