Grupos de Rutas
Los grupos de rutas usan paréntesis (folderName) para organizar rutas sin afectar la estructura de URLs. Permiten múltiples layouts en el mismo nivel de ruta.
Recipe
Tarjeta de receta de referencia rápida — lista para copiar-pegar.
app/
├── (marketing)/
│ ├── layout.tsx # Layout de marketing (sin nav, ancho completo)
│ ├── page.tsx # / (página de inicio)
│ ├── about/page.tsx # /about
│ └── pricing/page.tsx # /pricing
├── (app)/
│ ├── layout.tsx # Layout de aplicación (barra lateral, autenticación requerida)
│ ├── dashboard/page.tsx # /dashboard
│ └── settings/page.tsx # /settings
└── layout.tsx # Layout raíz (compartido por ambos grupos)
Regla clave: El nombre de carpeta entre paréntesis se elimina de la URL. (marketing)/about/page.tsx sirve /about, no /(marketing)/about.
Ejemplo Funcional
// app/layout.tsx — Layout raíz compartido por todos los grupos
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}// app/(marketing)/layout.tsx — Páginas públicas de marketing
import Link from "next/link";
export default function MarketingLayout({ children }: { children: React.ReactNode }) {
return (
<div>
<header className="flex items-center justify-between px-8 py-4">
<Link href="/" className="text-xl font-bold">Brand</Link>
<nav className="space-x-4">
<Link href="/about">About</Link>
<Link href="/pricing">Pricing</Link>
<Link href="/dashboard">Sign In</Link>
</nav>
</header>
<main>{children}</main>
</div>
);
}// app/(marketing)/page.tsx — Página de inicio en /
export default function HomePage() {
return (
<section className="py-20 text-center">
<h1 className="text-5xl font-bold">Welcome to Our Product</h1>
<p className="mt-4 text-lg text-gray-600">The best solution for your needs.</p>
</section>
);
}// app/(app)/layout.tsx — Páginas de aplicación autenticadas
import { auth } from "@/lib/auth";
import { redirect } from "next/navigation";
import Link from "next/link";
export default async function AppLayout({ children }: { children: React.ReactNode }) {
const session = await auth();
if (!session) redirect("/login");
return (
<div className="flex min-h-screen">
<aside className="w-64 border-r p-4">
<nav className="space-y-2">
<Link href="/dashboard" className="block">Dashboard</Link>
<Link href="/settings" className="block">Settings</Link>
</nav>
</aside>
<main className="flex-1 p-8">{children}</main>
</div>
);
}// app/(app)/dashboard/page.tsx — Dashboard en /dashboard
export default function DashboardPage() {
return <h1>Dashboard</h1>;
}Profundización
Cómo Funciona
- Las carpetas entre paréntesis son invisibles en la URL.
(marketing)nunca aparece en la URL — es puramente un mecanismo de organización. - Cada grupo puede tener su propio layout. Este es el caso de uso principal: diferentes carcasas visuales para diferentes secciones del sitio.
- Los grupos comparten el layout raíz. El
app/layout.tsxraíz aún envuelve todo. Los layouts de grupo se anidan dentro de él. - Puedes tener múltiples layouts raíz eliminando el nivel superior
app/layout.tsxy colocando unlayout.tsxcon<html>y<body>dentro de cada grupo. Cada grupo entonces tiene un documento completamente independiente. - Los grupos de rutas pueden estar anidados.
(marketing)/(campaigns)/page.tsxes válido — ambos segmentos de grupo se eliminan. - Los grupos no crean límites de ruta. Un
loading.tsxoerror.tsxdentro de un grupo funciona igual que en cualquier carpeta. - Sin conflictos entre grupos en la misma ruta. No puedes tener
(marketing)/about/page.tsxY(app)/about/page.tsx— ambas se resuelven a/abouty Next.js lanzará un error.
Variaciones
# Múltiples layouts raíz (documentos HTML completamente separados)
app/
├── (marketing)/
│ ├── layout.tsx # Debe incluir <html> y <body>
│ └── page.tsx # /
├── (app)/
│ ├── layout.tsx # Debe incluir <html> y <body>
│ └── dashboard/
│ └── page.tsx # /dashboard
# Sin app/layout.tsx de nivel superior en este patrón
// Usando grupos para separación de autenticación vs. público
// app/(auth)/layout.tsx
export default function AuthLayout({ children }: { children: React.ReactNode }) {
return (
<div className="flex min-h-screen items-center justify-center">
<div className="w-full max-w-md">{children}</div>
</div>
);
}
// app/(auth)/login/page.tsx → /login
// app/(auth)/register/page.tsx → /register# Grupos para organización de características (sin diferencia de layout)
app/
├── (features)/
│ ├── billing/page.tsx # /billing
│ └── invoices/page.tsx # /invoices
├── (admin)/
│ ├── layout.tsx # Layout de administrador con protección
│ └── users/page.tsx # /users
Notas de TypeScript
// Los layouts de grupo de ruta tienen los mismos tipos que cualquier layout
interface GroupLayoutProps {
children: React.ReactNode;
}
// No se necesitan tipos especiales — los grupos son puramente un concepto del sistema de archivos
// Los params de segmentos dinámicos por encima del grupo aún fluyen a través
// app/(app)/[orgId]/settings/page.tsx
interface SettingsPageProps {
params: Promise<{ orgId: string }>;
}Gotchas
- Dos grupos no pueden definir la misma ruta. Si
(a)/about/page.tsxy(b)/about/page.tsxambas existen, la compilación falla. - La página de inicio
/solo puede vivir en un grupo. Colocapage.tsxen el grupo que debe poseer la URL raíz. - Múltiples layouts raíz significa sin layout compartido. Si cada grupo tiene su propio
<html>, navegar entre grupos activa una recarga completa de la página. - Los grupos anidados se componen.
(a)/(b)/page.tsxestá permitido, pero ambas capas se eliminan — la URL es solo/. loading.tsxyerror.tsxen un grupo se aplican a todas las rutas dentro de ese grupo — no se filtran a otros grupos.- Middleware no sabe sobre grupos. Los matchers de middleware funcionan en rutas de URL reales (sin los nombres entre paréntesis).
Alternativas
| Enfoque | Cuándo Usar |
|---|---|
| Carpetas anidadas sin paréntesis | Cuando la carpeta debe aparecer en la URL |
Rutas Paralelas (@slot) | Renderizar múltiples vistas simultáneamente en un layout |
| Redirecciones basadas en middleware | Enrutar usuarios a diferentes secciones según autenticación o rol |
| Aplicaciones Next.js separadas | Despliegues completamente independientes para diferentes secciones |
FAQs
¿Aparece el nombre de carpeta entre paréntesis en la URL?
No. El nombre entre paréntesis se elimina completamente. (marketing)/about/page.tsx sirve /about, no /(marketing)/about.
¿Pueden dos grupos de rutas definir una página en la misma ruta URL?
No. Si (a)/about/page.tsx y (b)/about/page.tsx ambas existen, la compilación falla porque ambas se resuelven a /about.
¿Qué grupo de ruta debe poseer la página de inicio (/)?
Solo un grupo puede contener page.tsx en su raíz. Colócalo en el grupo que debe poseer la URL / (típicamente el grupo de marketing o público).
¿Qué sucede cuando navegas entre rutas en diferentes grupos que tienen layouts raíz separados?
Ocurre una recarga completa de la página. Cuando cada grupo tiene su propio <html> y <body> (múltiples layouts raíz), navegar entre grupos no puede ser una transición suave del lado del cliente.
¿Se filtran loading.tsx y error.tsx en un grupo de ruta a otros grupos?
No. Se aplican solo a rutas dentro de ese grupo. Los archivos límite de cada grupo están limitados a sus propias rutas.
Gotcha: ¿Conoce middleware los nombres de grupos de ruta?
No. Los matchers de middleware funcionan en rutas de URL reales. Los nombres de carpetas entre paréntesis son invisibles para middleware. Coincide en la URL real como /about, no /(marketing)/about.
¿Cómo creas múltiples layouts raíz?
Elimina el nivel superior app/layout.tsx y coloca un layout.tsx con <html> y <body> dentro de cada grupo.
app/
├── (marketing)/
│ ├── layout.tsx # Debe incluir <html> y <body>
│ └── page.tsx
├── (app)/
│ ├── layout.tsx # Debe incluir <html> y <body>
│ └── dashboard/page.tsx
¿Pueden anidarse los grupos de rutas?
Sí. (a)/(b)/page.tsx es válido. Ambos segmentos de grupo se eliminan, así que la URL es solo /.
¿Qué tipos de TypeScript usan los layouts de grupo de ruta?
Los mismos tipos que cualquier layout. No hay tipos especiales para grupos de rutas ya que son puramente un concepto del sistema de archivos.
interface GroupLayoutProps {
children: React.ReactNode;
}¿Cómo tipas una página dentro de un grupo de ruta que también tiene un segmento dinámico por encima?
// app/(app)/[orgId]/settings/page.tsx
interface SettingsPageProps {
params: Promise<{ orgId: string }>;
}Los params de segmentos dinámicos por encima del grupo aún fluyen normalmente.
Gotcha: Cuando usas grupos para separación de autenticación, ¿cómo proteges el grupo de aplicación?
Verifica la autenticación en el layout del grupo y redirige si no está autenticado.
// app/(app)/layout.tsx
import { auth } from "@/lib/auth";
import { redirect } from "next/navigation";
export default async function AppLayout({
children,
}: { children: React.ReactNode }) {
const session = await auth();
if (!session) redirect("/login");
return <div>{children}</div>;
}¿Son útiles los grupos de ruta incluso cuando no necesitas diferentes layouts?
Sí. Pueden usarse puramente para organización del sistema de archivos, agrupando rutas relacionadas sin afectar la estructura de URLs.
Relacionado
- Conceptos Básicos de App Router — descripción general de convenciones de archivos
- Layouts — cómo los layouts se componen y anidan
- Rutas Paralelas — segmentos @slot para renderizado lado a lado
- Middleware — lógica de enrutamiento a nivel de solicitud