Next.js
Route groups y private folders
Diferencia route groups y private folders para organizar layouts, áreas y código interno sin modificar las URLs públicas del App Router.
- Última actualización
- Actualizada
- Nivel
- Aplicación
Next.js
Diferencia route groups y private folders para organizar layouts, áreas y código interno sin modificar las URLs públicas del App Router.
Route groups y private folders modifican cómo organizas el árbol, no la URL pública. Un route group agrupa segmentos y permite aplicar layouts selectivamente; una private folder excluye todo un subárbol de las convenciones del router.
El App Router utiliza caracteres especiales para separar organización interna de estructura pública:
(folder)
→ grupo organizativo o frontera de layout
→ no aparece en el pathname
_folder
→ implementación privada excluida del routing
→ no aparece en el pathnameAunque ambos desaparecen de la URL, no son intercambiables.
Una URL debe ser estable y legible, mientras el código necesita organizarse por:
Sin estas convenciones, una carpeta de organización podría añadir accidentalmente segmentos a la URL:
app/marketing/pricing/page.tsx
→ /marketing/pricingSi la URL deseada es /pricing, un route group permite separar el área interna:
app/(marketing)/pricing/page.tsx
→ /pricingSe crean con paréntesis:
app/
├─ (marketing)/
│ ├─ layout.tsx
│ ├─ page.tsx
│ └─ pricing/page.tsx
└─ (dashboard)/
├─ layout.tsx
└─ dashboard/page.tsxURLs:
(marketing)/page → /
(marketing)/pricing/page → /pricing
(dashboard)/dashboard/page → /dashboardEl nombre del grupo no forma parte del path.
app/
├─ layout.tsx
├─ (shop)/
│ ├─ layout.tsx
│ ├─ products/page.tsx
│ └─ cart/page.tsx
└─ (account)/
├─ layout.tsx
└─ settings/page.tsxComposición:
/products
RootLayout → ShopLayout → ProductsPage
/settings
RootLayout → AccountLayout → SettingsPageEl grupo permite que rutas situadas al mismo nivel URL usen shells distintos sin añadir /shop ni /account.
Puedes omitir un root layout común y definir uno dentro de cada grupo:
app/
├─ (marketing)/
│ ├─ layout.tsx
│ └─ page.tsx
└─ (app)/
├─ layout.tsx
└─ dashboard/page.tsxCada root layout debe incluir <html> y <body>.
Navegar entre rutas que pertenecen a root layouts diferentes puede producir una carga completa del documento:
/ → /dashboard
soft navigation no garantizada
→ document request nuevo
→ state cliente se reiniciaUtiliza múltiples roots cuando las áreas son realmente independientes, no solo porque tienen estilos diferentes.
Los grupos no distinguen URLs públicas:
app/(shop)/about/page.tsx
app/(marketing)/about/page.tsxAmbas generan /about. Es un conflicto de build.
La unicidad se evalúa sobre el pathname final después de eliminar grupos.
app/(app)/(authenticated)/dashboard/page.tsxPuede expresar responsabilidades internas, pero demasiados niveles aumentan navegación mental. Si el grupo no aporta layout, ownership o frontera, probablemente sobra.
Se crean con underscore:
app/products/
├─ _components/
│ ├─ product-card.tsx
│ └─ product-filters.tsx
├─ _lib/
│ └─ parse-product-query.ts
└─ page.tsxTodo _components queda fuera del sistema de routing.
Un archivo normal product-card.tsx tampoco crea una ruta. Private folders aportan otras garantías editoriales:
No son un mecanismo de seguridad. El código puede importarse desde otros módulos si TypeScript lo permite.
Si realmente necesitas una URL que empiece por _, el nombre de carpeta debe codificarlo:
app/%5Finternal/page.tsx
→ /_internalSin encoding, _internal sería privado.
Estrategia:
app/dashboard/orders/
├─ _components/
├─ _actions/
├─ _schemas/
├─ loading.tsx
├─ error.tsx
└─ page.tsxCambios de la feature permanecen juntos.
La lógica puede quedar acoplada al router y ser difícil de reutilizar en jobs, scripts, API externa o tests.
Una solución híbrida:
src/
├─ app/dashboard/orders/
│ ├─ _components/
│ └─ page.tsx
└─ features/orders/
├─ domain/
├─ data/
└─ use-cases/La ruta coordina; el módulo de dominio conserva lógica reutilizable.
app/
├─ (public)/
│ ├─ page.tsx
│ ├─ pricing/page.tsx
│ └─ contact/page.tsx
├─ (auth)/
│ ├─ login/page.tsx
│ └─ register/page.tsx
└─ (workspace)/
└─ dashboard/page.tsxEste diseño hace visible el tipo de experiencia sin contaminar URLs.
No presupone autorización. El grupo (workspace) solo organiza; una persona puede solicitar /dashboard directamente.
En una organización grande:
app/
├─ (commerce)/
├─ (support)/
└─ (accounts)/Puede reflejar ownership. Para evitar dependencias cruzadas, acompáñalo con APIs públicas de módulos, lint de imports y CODEOWNERS.
El nombre de carpeta por sí solo no impide acoplamiento.
Usa un segmento normal cuando forma parte del modelo público:
/admin/users
/docs/react
/organizations/acmeUsa un group cuando solo organiza código o layouts:
(marketing)
(auth)
(workspace)Pregunta:
¿Este nombre debe formar parte de links, analytics y contratos externos?
Si la respuesta es sí, probablemente es un segmento real.
Aporta colocation con ruta y dependencias locales.
Aporta separación del router y reutilización más explícita.
No existe una única estructura correcta. Elige según alcance:
_components.components/ui.app/
├─ (public)/
│ ├─ layout.tsx
│ └─ page.tsx
├─ (platform)/
│ └─ admin/page.tsx
└─ (tenant)/
└─ organizations/[organizationId]/
├─ layout.tsx
├─ _components/
├─ _actions/
└─ dashboard/page.tsx(public) usa un layout de marketing.(platform) separa administración global.(tenant) organiza rutas dependientes de organización.[organizationId] sí aparece en la URL porque identifica tenant._actions contiene adaptadores de mutación locales.El group mejora lectura; la seguridad proviene de session y Data Access Layer.
Ninguna de estas carpetas:
(protected)
(private)
_secureimpide que un usuario solicite una URL ni que un módulo termine en un bundle cliente.
Para seguridad:
server-only en módulos de servidor.Si no hay app/layout.tsx, / debe vivir dentro de uno de los grupos que tenga root layout.
Los nombres pueden repetirse en ramas diferentes si no provocan conflictos de paths, pero reduce claridad.
Nunca incluyas paréntesis en href:
<Link href="/pricing">Pricing</Link>El group no forma parte de la URL.
Es técnicamente posible. “Private” se refiere al router, no al módulo.
Cada grupo puede definir metadata y layouts diferentes, pero canonical URLs continúan sin el nombre del grupo.
El árbol se llena de paréntesis sin aportar semántica.
No autoriza nada.
Provoca document reloads y pérdida de state.
Dificulta reutilización y puede crear imports circulares.
No controla imports ni exposición en bundle.
Aunque no cambia URL, puede cambiar layout, boundaries, loading y root transitions.
next build y busca colisiones.app.(admin) y _admin?_server no protege secretos?app?Parallel e intercepting routes utiliza slots y contexto de navegación para modelar pantallas simultáneas y modales con URL propia.