layout.js

El archivo layout se utiliza para definir un diseño en su aplicación Next.js.

export default function DashboardLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return <section>{children}</section>
}
export default function DashboardLayout({ children }) {
  return <section>{children}</section>
}

Un diseño raíz (root layout) es el diseño superior en el directorio raíz app. Se utiliza para definir las etiquetas <html> y <body> y otros elementos de interfaz de usuario compartidos globalmente.

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  )
}
export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  )
}

Referencia

Props

children (requerido)

Los componentes de diseño deben aceptar y usar una prop children. Durante el renderizado, children se completará con los segmentos de ruta que el diseño está envolviendo. Estos serán principalmente el componente de un Layout hijo (si existe) o Page, pero también podrían ser otros archivos especiales como Loading o Error cuando corresponda.

params (opcional)

Una promesa que se resuelve en un objeto que contiene los parámetros de ruta dinámica desde el segmento raíz hasta ese diseño.

export default async function Layout({
  params,
}: {
  params: Promise<{ team: string }>
}) {
  const { team } = await params
}
export default async function Layout({ params }) {
  const { team } = await params
}
Ruta de ejemploURLparams
app/dashboard/[team]/layout.js/dashboard/1Promise<{ team: '1' }>
app/shop/[tag]/[item]/layout.js/shop/1/2Promise<{ tag: '1', item: '2' }>
app/blog/[...slug]/layout.js/blog/1/2Promise<{ slug: ['1', '2'] }>
  • Dado que la prop params es una promesa, debe usar async/await o la función use de React para acceder a los valores.
    • En la versión 14 y anteriores, params era una prop síncrona. Para ayudar con la compatibilidad hacia atrás, aún puede acceder a ella de forma síncrona en Next.js 15, pero este comportamiento quedará obsoleto en el futuro.

Diseño raíz (Root Layout)

El directorio app debe incluir un app/layout.js raíz.

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html>
      <body>{children}</body>
    </html>
  )
}
export default function RootLayout({ children }) {
  return (
    <html>
      <body>{children}</body>
    </html>
  )
}
  • El diseño raíz debe definir las etiquetas <html> y <body>.
    • No debe agregar manualmente etiquetas <head> como <title> y <meta> a los diseños raíz. En su lugar, debe usar la API de Metadata que maneja automáticamente requisitos avanzados como la transmisión en flujo (streaming) y la desduplicación de elementos <head>.
  • Puede usar grupos de rutas (route groups) para crear múltiples diseños raíz.
    • Navegar entre múltiples diseños raíz causará una recarga completa de la página (en lugar de una navegación del lado del cliente). Por ejemplo, navegar desde /cart que usa app/(shop)/layout.js a /blog que usa app/(marketing)/layout.js causará una recarga completa de la página. Esto solo aplica para múltiples diseños raíz.

Advertencias

Objeto de solicitud (Request Object)

Los diseños se almacenan en caché en el cliente durante la navegación para evitar solicitudes innecesarias al servidor.

Los diseños (Layouts) no se vuelven a renderizar. Pueden almacenarse en caché y reutilizarse para evitar cálculos innecesarios al navegar entre páginas. Al restringir el acceso de los diseños a la solicitud cruda, Next.js puede evitar la ejecución de código de usuario potencialmente lento o costoso dentro del diseño, lo que podría afectar negativamente el rendimiento.

Para acceder al objeto de solicitud, puede usar las APIs headers y cookies en Componentes del Servidor (Server Components) y Funciones.

import { cookies } from 'next/headers'

export default async function Layout({ children }) {
  const cookieStore = await cookies()
  const theme = cookieStore.get('theme')
  return '...'
}
import { cookies } from 'next/headers'

export default async function Layout({ children }) {
  const cookieStore = await cookies()
  const theme = cookieStore.get('theme')
  return '...'
}

Parámetros de consulta (Query params)

Los diseños no se vuelven a renderizar en la navegación, por lo que no pueden acceder a los parámetros de búsqueda que de otro modo quedarían obsoletos.

Para acceder a los parámetros de consulta actualizados, puede usar la prop searchParams de la Página, o leerlos dentro de un Componente del Cliente usando el hook useSearchParams. Dado que los Componentes del Cliente se vuelven a renderizar durante la navegación, tienen acceso a los últimos parámetros de consulta.

'use client'

import { useSearchParams } from 'next/navigation'

export default function Search() {
  const searchParams = useSearchParams()

  const search = searchParams.get('search')

  return '...'
}
'use client'

import { useSearchParams } from 'next/navigation'

export default function Search() {
  const searchParams = useSearchParams()

  const search = searchParams.get('search')

  return '...'
}
import Search from '@/app/ui/search'

export default function Layout({ children }) {
  return (
    <>
      <Search />
      {children}
    </>
  )
}
import Search from '@/app/ui/search'

export default function Layout({ children }) {
  return (
    <>
      <Search />
      {children}
    </>
  )
}

Pathname

Los diseños no se vuelven a renderizar en la navegación, por lo que no acceden al pathname que de otro modo quedaría obsoleto.

Para acceder al pathname actual, puede leerlo dentro de un Componente del Cliente usando el hook usePathname. Dado que los Componentes del Cliente se vuelven a renderizar durante la navegación, tienen acceso al último pathname.

'use client'

import { usePathname } from 'next/navigation'

// Lógica simplificada de breadcrumbs
export default function Breadcrumbs() {
  const pathname = usePathname()
  const segments = pathname.split('/')

  return (
    <nav>
      {segments.map((segment, index) => (
        <span key={index}>
          {' > '}
          {segment}
        </span>
      ))}
    </nav>
  )
}
'use client'

import { usePathname } from 'next/navigation'

// Lógica simplificada de breadcrumbs
export default function Breadcrumbs() {
  const pathname = usePathname()
  const segments = pathname.split('/')

  return (
    <nav>
      {segments.map((segment, index) => (
        <span key={index}>
          {' > '}
          {segment}
        </span>
      ))}
    </nav>
  )
}
import { Breadcrumbs } from '@/app/ui/Breadcrumbs'

export default function Layout({ children }) {
  return (
    <>
      <Breadcrumbs />
      <main>{children}</main>
    </>
  )
}
import { Breadcrumbs } from '@/app/ui/Breadcrumbs'

export default function Layout({ children }) {
  return (
    <>
      <Breadcrumbs />
      <main>{children}</main>
    </>
  )
}

Obtención de datos (Fetching Data)

Los diseños no pueden pasar datos a sus children. Sin embargo, puede obtener los mismos datos en una ruta más de una vez y usar cache de React para desduplicar las solicitudes sin afectar el rendimiento.

Alternativamente, cuando se usa fetch en Next.js, las solicitudes se desduplican automáticamente.

export async function getUser(id: string) {
  const res = await fetch(`https://.../users/${id}`)
  return res.json()
}
import { getUser } from '@/app/lib/data'
import { UserName } from '@/app/ui/user-name'

export default async function Layout({ children }) {
  const user = await getUser('1')

  return (
    <>
      <nav>
        {/* ... */}
        <UserName user={user.name} />
      </nav>
      {children}
    </>
  )
}
import { getUser } from '@/app/lib/data'
import { UserName } from '@/app/ui/user-name'

export default async function Layout({ children }) {
  const user = await getUser('1')

  return (
    <>
      <nav>
        {/* ... */}
        <UserName user={user.name} />
      </nav>
      {children}
    </>
  )
}
import { getUser } from '@/app/lib/data'
import { UserName } from '@/app/ui/user-name'

export default async function Page() {
  const user = await getUser('1')

  return (
    <div>
      <h1>Welcome {user.name}</h1>
    </div>
  )
}
import { getUser } from '@/app/lib/data'
import { UserName } from '@/app/ui/user-name'

export default async function Page() {
  const user = await getUser('1')

  return (
    <div>
      <h1>Welcome {user.name}</h1>
    </div>
  )
}

Acceso a segmentos hijos

Los diseños no tienen acceso a los segmentos de ruta debajo de sí mismos. Para acceder a todos los segmentos de ruta, puede usar useSelectedLayoutSegment o useSelectedLayoutSegments en un Componente del Cliente.

'use client'

import Link from 'next/link'
import { useSelectedLayoutSegment } from 'next/navigation'

export default function NavLink({
  slug,
  children,
}: {
  slug: string
  children: React.ReactNode
}) {
  const segment = useSelectedLayoutSegment()
  const isActive = slug === segment

  return (
    <Link
      href={`/blog/${slug}`}
      // Cambiar estilo dependiendo de si el enlace está activo
      style={{ fontWeight: isActive ? 'bold' : 'normal' }}
    >
      {children}
    </Link>
  )
}
'use client'

import Link from 'next/link'
import { useSelectedLayoutSegment } from 'next/navigation'

export default function NavLinks({ slug, children }) {
  const segment = useSelectedLayoutSegment()
  const isActive = slug === segment

  return (
    <Link
      href={`/blog/${slug}`}
      style={{ fontWeight: isActive ? 'bold' : 'normal' }}
    >
      {children}
    </Link>
  )
}
import { NavLink } from './nav-link'
import getPosts from './get-posts'

export default async function Layout({
  children,
}: {
  children: React.ReactNode
}) {
  const featuredPosts = await getPosts()
  return (
    <div>
      {featuredPosts.map((post) => (
        <div key={post.id}>
          <NavLink slug={post.slug}>{post.title}</NavLink>
        </div>
      ))}
      <div>{children}</div>
    </div>
  )
}
import { NavLink } from './nav-link'
import getPosts from './get-posts'

export default async function Layout({ children }) {
  const featuredPosts = await getPosts()
  return (
    <div>
      {featuredPosts.map((post) => (
        <div key={post.id}>
          <NavLink slug={post.slug}>{post.title}</NavLink>
        </div>
      ))}
      <div>{children}</div>
    </div>
  )
}

Ejemplos

Metadata

Puede modificar los elementos HTML <head> como title y meta usando el objeto metadata o la función generateMetadata.

import type { Metadata } from 'next'

export const metadata: Metadata = {
  title: 'Next.js',
}

export default function Layout({ children }: { children: React.ReactNode }) {
  return '...'
}
export const metadata = {
  title: 'Next.js',
}

export default function Layout({ children }) {
  return '...'
}

Nota importante: No debe agregar manualmente etiquetas <head> como <title> y <meta> a los diseños raíz. En su lugar, use las APIs de Metadata que manejan automáticamente requisitos avanzados como la transmisión en flujo (streaming) y la desduplicación de elementos <head>.

Enlaces de navegación activos

Puede usar el hook usePathname para determinar si un enlace de navegación está activo.

Dado que usePathname es un hook del cliente, necesita extraer los enlaces de navegación en un Componente del Cliente, que puede importar en su diseño:

'use client'

import { usePathname } from 'next/navigation'
import Link from 'next/link'

export function NavLinks() {
  const pathname = usePathname()

  return (
    <nav>
      <Link className={`link ${pathname === '/' ? 'active' : ''}`} href="/">
        Inicio
      </Link>

      <Link
        className={`link ${pathname === '/about' ? 'active' : ''}`}
        href="/about"
      >
        Acerca de
      </Link>
    </nav>
  )
}
'use client'

import { usePathname } from 'next/navigation'
import Link from 'next/link'

export function Links() {
  const pathname = usePathname()

  return (
    <nav>
      <Link className={`link ${pathname === '/' ? 'active' : ''}`} href="/">
        Inicio
      </Link>

      <Link
        className={`link ${pathname === '/about' ? 'active' : ''}`}
        href="/about"
      >
        Acerca de
      </Link>
    </nav>
  )
}
import { NavLinks } from '@/app/ui/nav-links'

export default function Layout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <NavLinks />
        <main>{children}</main>
      </body>
    </html>
  )
}
import { NavLinks } from '@/app/ui/nav-links'

export default function Layout({ children }) {
  return (
    <html lang="en">
      <body>
        <NavLinks />
        <main>{children}</main>
      </body>
    </html>
  )
}

Mostrar contenido basado en params

Utilizando segmentos de ruta dinámicos, puede mostrar o recuperar contenido específico basado en la propiedad params.

export default async function DashboardLayout({
  children,
  params,
}: {
  children: React.ReactNode
  params: Promise<{ team: string }>
}) {
  const { team } = await params

  return (
    <section>
      <header>
        <h1>Bienvenido al Panel de {team}</h1>
      </header>
      <main>{children}</main>
    </section>
  )
}
export default async function DashboardLayout({ children, params }) {
  const { team } = await params

  return (
    <section>
      <header>
        <h1>Bienvenido al Panel de {team}</h1>
      </header>
      <main>{children}</main>
    </section>
  )
}

Leer params en Componentes Cliente

Para usar params en un Componente Cliente (que no puede ser async), puede utilizar la función use de React para leer la promesa:

'use client'

import { use } from 'react'

export default function Page({
  params,
}: {
  params: Promise<{ slug: string }>
}) {
  const { slug } = use(params)
}
'use client'

import { use } from 'react'

export default function Page({ params }) {
  const { slug } = use(params)
}

Historial de Versiones

VersiónCambios
v15.0.0-RCparams ahora es una promesa. Hay disponible un codemod.
v13.0.0Se introdujo layout.