Grupos de rutas y organizando app/
(auth), (tabs), y convenciones de carpetas privadas. Los grupos de rutas mantienen árboles app/ grandes navegables sin contaminar las URLs o las rutas de enlace profundo.
Busca en todas las páginas de la documentación
app/(auth), (tabs), y convenciones de carpetas privadas. Los grupos de rutas mantienen árboles app/ grandes navegables sin contaminar las URLs o las rutas de enlace profundo.
Tarjeta de receta de referencia rápida - lista para copiar y pegar.
app/
├── _layout.tsx # Proveedores raíz + navegador superior
├── index.tsx # Hub de redirección de entrada
├── (auth)/
│ ├── _layout.tsx # Stack de autenticación (sin encabezado)
│ ├── login.tsx # /login
│ └── register.tsx # /register
├── (app)/
│ ├── _layout.tsx # Shell de guarda autenticado
│ └── (tabs)/
│ ├── _layout.tsx
│ ├── index.tsx # /
│ └── settings.tsx # /settings
├── (modals)/
│ ├── _layout.tsx # presentation: modal
│ └── compose.tsx # /compose
└── _components/ # Privado - NO es una ruta
└── ScreenChrome.tsx// app/(app)/_layout.tsx - shell de auth mínimo
import { Redirect, Stack } from "expo-router";
import { useSession } from "@/features/auth";
export default function AppShellLayout() {
const { session, isLoading } = useSession();
if (isLoading) return null;
if (!session) return <Redirect href="/login" />;
return <Stack screenOptions={{ headerShown: false }} />;
}Cuándo usarlo:
presentation: "modal"/tabs en cada URL_components/ junto a layoutsUn árbol con forma de producción con grupo de auth, shell de app, tabs, modals, y carpetas privadas.
// app/_layout.tsx
import { Stack } from "expo-router";
import { AppProviders } from "@/app-providers";
export default function RootLayout() {
return (
<AppProviders>
<Stack screenOptions={{ headerShown: false }}>
<Stack.Screen name="index" />
<Stack.Screen name="(auth)" />
<Stack.Screen name="(app)" />
<Stack.Screen name="(modals)" options={{ presentation: "modal" }} />
</Stack>
</AppProviders>
);
}// app/(auth)/_layout.tsx
import { Stack } from "expo-router";
export default function AuthLayout() {
return (
<Stack screenOptions={{ headerShown: false, animation: "fade" }}>
<Stack.Screen name="login" />
<Stack.Screen name="register" />
</Stack>
);
}// app/(modals)/_layout.tsx
import { Stack } from "expo-router";
export default function ModalsLayout() {
return (
<Stack
screenOptions={{
presentation: "modal",
headerShown: true,
}}
/>
);
}// app/(app)/(tabs)/_layout.tsx
import { Tabs } from "expo-router";
export default function TabsLayout() {
return (
<Tabs>
<Tabs.Screen name="index" options={{ title: "Home" }} />
<Tabs.Screen name="settings" options={{ title: "Settings" }} />
</Tabs>
);
}// app/_components/LoadingGate.tsx - importa desde layouts; nunca ruta
import { ActivityIndicator, View } from "react-native";
export function LoadingGate() {
return (
<View style={{ flex: 1, justifyContent: "center", alignItems: "center" }}>
<ActivityIndicator />
</View>
);
}Mapa de URL (grupos omitidos de la ruta):
/login - (auth)/login.tsx
/register - (auth)/register.tsx
/ - (app)/(tabs)/index.tsx
/settings - (app)/(tabs)/settings.tsx
/compose - (modals)/compose.tsx| Patrón | Impacto de URL | Propósito |
|---|---|---|
(auth)/login.tsx | /login | Nombre del grupo omitido de la URL |
(tabs)/index.tsx | / | Organizar archivos de tabs |
[id].tsx | /:id | Segmento dinámico (no es un grupo) |
_layout.tsx | - | Navegador para carpeta |
_components/ | - | Carpeta privada (prefijo subrayado) |
(app)/(tabs)/ es válido - ambos nombres se eliminan de la URL_layout.tsx por carpeta para navegadoresapp/ features/
├── ruteo y layouts solo ├── lógica de negocio
├── re-exportaciones ├── hooks y API
├── redirecciones └── pantallas de UI
└── _components/ (privado)(auth) para sign-in, (app) para shell autenticado, (modals) para overlaysapp/(app)/(tabs)/orders/ mapea a features/orders/index.tsx raíz delgado - hub de redirección solo; ver Redirects & Index Routesapp/(app)/(tabs)/orders/[id].tsx es suficiente; rutas más profundas señalan tiempo de refactorapp/(tabs)/
├── _layout.tsx
├── _components/
│ └── TabIcon.tsx
├── index.tsx
└── profile.tsx_prefix marca archivos/carpetas como no-rutable - Expo Router los ignora para generación de URL_components/ - no los pongas en features/ a menos que se reutilicen+html.tsx, +not-found.tsx son archivos especiales - aprende sus roles antes de personalizar web/not-found// app/_layout.tsx - el registro explícito previene auto-descubrimiento ambiguo
<Stack>
<Stack.Screen name="(auth)" />
<Stack.Screen name="(app)" />
</Stack>El explícito <Stack.Screen name="(auth)" /> documenta segmentos de nivel superior para revisores y mantiene opciones de pantalla centralizadas.
Rutas duplicadas entre grupos - (auth)/index.tsx y (app)/index.tsx ambas mapean a /. Solución: Hub de redirección único app/index.tsx; un grupo es dueño de /.
Olvidar paréntesis de grupo - auth/login.tsx crea /auth/login, rompiendo enlaces de marketing. Solución: Renombra a (auth)/login.tsx.
Lógica de negocio en _layout.tsx - la fetching de datos en layouts se ejecuta en cada navegación de hijo. Solución: Solo redirección de auth; las pantallas son dueñas de datos.
Grupo (app) omnipotente - cada pantalla volcada bajo un grupo. Solución: Sub-grupos (tabs), (modals), carpetas de feature.
_components sin subrayado - components/Header.tsx se convierte en ruta /components/Header. Solución: Prefija carpetas privadas con _.
router.push entre grupos con ruta incorrecta - empujar /(app)/settings incluye grupo en algunas APIs. Solución: Usa rutas de URL /settings u objetos Href tipados.
Monorepo compartido app/ - apps white-label divergen en árboles de ruta. Solución: Separa apps/brand-a/app/ por binario - ver ../project-setup/multiple-apps-in-one-repo/multiple-apps-in-one-repo.md.
| Alternativa | Usar cuándo | No usar cuándo |
|---|---|---|
Grupos de ruta (name) | Organizar sin segmentos de URL | Necesitas /auth/login en la URL pública |
Árbol app/ plano | <10 rutas totales | Escuadras paralelas editando una carpeta diariamente |
Manual src/routes React Navigation | Brownfield sin ruteo basado en archivos | Apps greenfield nuevas SDK 57 |
| Feature flags para secciones | Tabs de temporada | División permanente de auth vs app |
| Apps Expo separadas | Productos verdaderamente diferentes | Variantes white-label - usa config en su lugar |
| Espejo profundo de carpeta del organigrama | Nunca recomendado | - |
No. (auth)/login.tsx mapea a /login, no a /auth/login. Los grupos existen puramente para organización del sistema de archivos y límites de layout.
Sí - app/(app)/(tabs)/(hidden)/ es válido. Cada nombre de grupo se elimina. Evita anidamiento innecesario que oscurezca el mapa de ruta.
Los archivos y carpetas que comienzan con _ (excepto _layout.tsx) no se convierten en rutas. Usa _components/, _hooks/, etc., para código colocado no-ruta.
Los enlaces profundos apuntan a rutas de URL, no a nombres de grupo. /settings abre (app)/(tabs)/settings.tsx independientemente de carpetas de grupo. Configura scheme en app.config.ts una vez a nivel de app.
Sí - un app/_layout.tsx monta proveedores para ambos grupos. Divide navegadores por grupo ((auth)/_layout, (app)/_layout), no árboles de proveedor duplicados.
Crea (modals)/ con su propio _layout.tsx configurando presentation: "modal". Registra (modals) en el stack raíz. Las URLs permanecen como /compose, no /modals/compose.
Ambos se resolverían a / - un conflicto. Usa un único app/index.tsx como redirección de entrada en su lugar.
app/ es la app layer FSD (ruteo solo). features/, entities/, shared/ viven fuera de app/. Los grupos de ruta segmentan flujos; las slices de feature segmentan dominios de negocio - ver ../architecture-design/feature-sliced-design-for-rn/feature-sliced-design-for-rn.md.
Cuando marketing, SEO (web), o docs de soporte publiquen la ruta - p. ej. /pricing para una página web pública. Usa carpetas normales, no grupos, cuando el segmento debe aparecer en la URL.
Mantén una tabla docs/routes.md: URL - ruta de archivo - propietario de feature. Actualiza en cada nuevo grupo. Empareja con rutas tipadas para que TypeScript detecte divergencia.
app/index.tsx(tabs) y (drawer)app/Versiones de Stack: Esta página fue escrita para React 19.2.3, React Native 0.86.0, y Expo SDK 57 (
expo~57.0.4).
Revisado por Chris St. John·Última actualización: 16 jul 2026