Ir al contenido
angel.library

Layouts anidados

Un layout.tsx envuelve page.tsx y todo lo que haya debajo en su carpeta — pero a diferencia de un componente normal, no se remonta al navegar entre páginas hijas. Un sidebar con estado propio, un contador, un video reproduciéndose: todo eso sobrevive la navegación si vive en un layout, exactamente igual que transition:persist en Astro pero sin tener que declararlo — es el comportamiento por defecto aquí.

app/dashboard/layout.tsx
export default function DashboardLayout({
children
}: {
children: React.ReactNode
}) {
return (
<section>
<Sidebar />
{children}
</section>
)
}

app/layout.tsx es especial: es el único lugar donde van las etiquetas <html> y <body>. Toda la app necesita exactamente uno (salvo que uses múltiples layouts raíz con route groups, ver abajo).

app/layout.tsx
export default function RootLayout({
children
}: {
children: React.ReactNode
}) {
return (
<html lang="es">
<body>{children}</body>
</html>
)
}

No le agregues <title>/<meta> a mano aquí — eso es trabajo de la Metadata API.

Cada carpeta puede tener su propio layout.tsx, que envuelve al de la carpeta padre — se van anidando en cascada, de afuera hacia adentro.

app/
layout.tsx (envuelve todo el sitio)
dashboard/
layout.tsx (envuelve solo /dashboard/*)
page.tsx
ajustes/
page.tsx (usa ambos layouts, anidados)

Una carpeta entre paréntesis, (nombre), agrupa rutas para compartir un layout (o simplemente para organizar el proyecto) sin que ese nombre aparezca en la URL.

app/
(marketing)/
layout.tsx (layout propio, solo para estas rutas)
page.tsx → /
precios/
page.tsx → /precios
(panel)/
layout.tsx (otro layout, distinto del anterior)
dashboard/
page.tsx → /dashboard

(marketing) y (panel) no aparecen en la URL — son puramente organizativos. Esto también es la forma de tener más de un layout raíz: cualquier layout que no tiene otro layout.tsx por encima (dentro de su route group) actúa como raíz para esa sección, con su propio <html>/<body>.

Como no se vuelve a renderizar en cada navegación, un layout no tiene acceso confiable a searchParams ni a la ruta actual, pues quedarían obsoletos al navegar. Para ello, extrae esa parte a un Client Component con usePathname o useSearchParams e impórtalo en el layout.

app/dashboard/layout.tsx
import { Breadcrumbs } from "@/app/ui/breadcrumbs"
// Client Component con usePathname
export default function Layout({ children }: { children: React.ReactNode }) {
return (
<>
<Breadcrumbs />
<main>{children}</main>
</>
)
}
ConceptoQué es
layout.tsxUI compartida, no se remonta al navegar entre páginas hijas
Layout raíz (app/layout.tsx)Obligatorio, define <html>/<body>
Layouts anidadosUno por carpeta, se envuelven en cascada
Route group (nombre)Agrupa/organiza rutas sin afectar la URL, puede tener su propio layout raíz
template.tsxComo un layout, pero SÍ se remonta en cada navegación (poco común, casos con animación de entrada por página)
  • Un layout no puede pasarle datos a children vía props — si layout y page necesitan el mismo dato, pedilo en los dos (con fetch, que memoiza automáticamente, o con cache de React si no usas fetch).
  • Navegar entre dos layouts raíz distintos (dos route groups distintos) fuerza una recarga completa de página, no una transición de cliente — es la excepción a “los layouts no se remontan”.
  • template.tsx existe para el caso raro donde SÍ quieres que algo se reinicie en cada navegación (un formulario que debe limpiarse, una animación de entrada que debe repetirse) — la mayoría de las veces, layout.tsx es lo correcto.