Los Layouts y Templates son fundamentales para crear aplicaciones escalables y mantener consistencia visual en Next.js. Te explico todo lo que necesitas saber.
Un Layout es un componente que envuelve múltiples páginas y mantiene su estado entre navegaciones. Se define con layout.tsx.
A continuación, se muestra la estructura básica de layouts en Next.js.
app/
├── layout.tsx # Layout raíz (obligatorio)
├── page.tsx # Página principal
└── dashboard/
├── layout.tsx # Layout específico
└── page.tsx # Página del dashboard El Layout raíz es el layout más alto en la jerarquía. Se define en app/layout.tsx y se envuelve en <html> y <body>.
// app/layout.tsx
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="es">
<body>
<header>
<nav>Mi App</nav>
</header>
<main>{children}</main>
<footer>© 2024</footer>
</body>
</html>
)
} Un Layout específico se define en carpetas anidadas y envuelve solo las páginas dentro de esa carpeta. Se define con layout.tsx.
// app/dashboard/layout.tsx
export default function DashboardLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<div className="dashboard">
<aside>
<nav>
<a href="/dashboard">Inicio</a>
<a href="/dashboard/users">Usuarios</a>
<a href="/dashboard/settings">Configuración</a>
</nav>
</aside>
<div className="content">
{children}
</div>
</div>
)
} // app/blog/layout.tsx
'use client'
import { useState } from 'react'
export default function BlogLayout({
children,
}: {
children: React.ReactNode
}) {
// Este estado se mantiene entre navegaciones
const [sidebarOpen, setSidebarOpen] = useState(false)
return (
<div className="blog-layout">
<button onClick={() => setSidebarOpen(!sidebarOpen)}>
Toggle Sidebar
</button>
<div className={`sidebar ${sidebarOpen ? 'open' : ''}`}>
<h3>Categorías</h3>
<ul>
<li><a href="/blog/react">React</a></li>
<li><a href="/blog/nextjs">Next.js</a></li>
<li><a href="/blog/javascript">JavaScript</a></li>
</ul>
</div>
<main className="blog-content">
{children}
</main>
</div>
)
} // app/blog/page.tsx
export default function BlogPage() {
// Esta página se re-renderiza, pero el layout no
return (
<div>
<h1>Blog Principal</h1>
<p>Lista de artículos...</p>
</div>
)
} Los layouts se pueden anidar para crear estructuras complejas:
app/
├── layout.tsx # Layout global
├── (marketing)/
│ ├── layout.tsx # Layout para marketing
│ ├── page.tsx # Landing page
│ └── about/
│ └── page.tsx # About page
└── dashboard/
├── layout.tsx # Layout para dashboard
├── page.tsx # Dashboard home
└── analytics/
├── layout.tsx # Layout específico analytics
└── page.tsx # Analytics page El Layout de Marketing se define en la carpeta (marketing)/ y envuelve las páginas relacionadas con el marketing, como la landing page y la página “Nosotros”.
// app/(marketing)/layout.tsx
export default function MarketingLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<div className="marketing">
{/* Header específico para marketing */}
<header className="marketing-header">
<nav>
<a href="/">Inicio</a>
<a href="/about">Nosotros</a>
<a href="/contact">Contacto</a>
<button className="cta-button">Registrarse</button>
</nav>
</header>
{children}
{/* Footer específico para marketing */}
<footer className="marketing-footer">
<div className="newsletter">
<h3>Suscríbete a nuestro newsletter</h3>
<input type="email" placeholder="tu@email.com" />
<button>Suscribirse</button>
</div>
</footer>
</div>
)
} El Layout de Dashboard se define en la carpeta dashboard/ y envuelve las páginas relacionadas con el dashboard, como la página de inicio y la página de usuarios.
import { Sidebar } from './components/Sidebar'
import { TopBar } from './components/TopBar'
export default function DashboardLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<div className="dashboard-layout">
<TopBar />
<div className="dashboard-body">
<Sidebar />
<main className="dashboard-main">
{children}
</main>
</div>
</div>
)
} El Layout específico para Analytics se define en la carpeta dashboard/analytics/ y envuelve la página de analytics.
export default function AnalyticsLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<div className="analytics-layout">
{/* Toolbar específico para analytics */}
<div className="analytics-toolbar">
<select>
<option>Últimos 7 días</option>
<option>Últimos 30 días</option>
<option>Último año</option>
</select>
<button>Exportar</button>
</div>
{children}
</div>
)
} Los Templates son similares a los layouts, pero se re-renderizan en cada navegación. Útiles cuando necesitas:
'use client'
import { motion } from 'framer-motion'
export default function Template({
children,
}: {
children: React.ReactNode
}) {
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
transition={{ duration: 0.3 }}
>
{children}
</motion.div>
)
} Son templates que se re-renderizan en cada navegación, y se utilizan para agregar efectos visuales o de interacción.
'use client'
import { useEffect } from 'react'
export default function BlogTemplate({
children,
}: {
children: React.ReactNode
}) {
useEffect(() => {
// Se ejecuta en cada navegación
console.log('Nueva página cargada')
// Analytics
gtag('config', 'GA_MEASUREMENT_ID', {
page_title: document.title,
page_location: window.location.href,
})
}, [])
return (
<div className="blog-template">
<div className="page-transition">
{children}
</div>
</div>
)
} Los componentes reutilizables son elementos de UI que se pueden utilizar en múltiples páginas. Se definen en la carpeta components/ y se utilizan en los layouts y templates.
interface PageHeaderProps {
title: string
subtitle?: string
breadcrumbs?: Array<{
label: string
href: string
}>
}
export function PageHeader({ title, subtitle, breadcrumbs }: PageHeaderProps) {
return (
<header className="page-header">
{breadcrumbs && (
<nav className="breadcrumbs">
{breadcrumbs.map((crumb, index) => (
<span key={index}>
<a href={crumb.href}>{crumb.label}</a>
{index < breadcrumbs.length - 1 && ' / '}
</span>
))}
</nav>
)}
<h1>{title}</h1>
{subtitle && <p className="subtitle">{subtitle}</p>}
</header>
)
} A continuación, se muestra cómo utilizar el partial PageHeader en una página de usuarios.
import { PageHeader } from '../../components/PageHeader'
export default function UsersPage() {
return (
<div>
<PageHeader
title="Gestión de Usuarios"
subtitle="Administra los usuarios de tu aplicación"
breadcrumbs={[
{ label: 'Dashboard', href: '/dashboard' },
{ label: 'Usuarios', href: '/dashboard/users' }
]}
/>
<div className="users-content">
{/* Contenido de usuarios */}
</div>
</div>
)
} El Template de formulario reutilizable se define en la carpeta components/ y se utiliza para crear formularios consistentes en toda la aplicación.
interface FormTemplateProps {
title: string
children: React.ReactNode
onSubmit: (e: React.FormEvent) => void
submitText?: string
isLoading?: boolean
}
export function FormTemplate({
title,
children,
onSubmit,
submitText = 'Guardar',
isLoading = false
}: FormTemplateProps) {
return (
<div className="form-template">
<div className="form-header">
<h2>{title}</h2>
</div>
<form onSubmit={onSubmit} className="form-body">
{children}
<div className="form-actions">
<button
type="submit"
disabled={isLoading}
className="btn-primary"
>
{isLoading ? 'Guardando...' : submitText}
</button>
<button type="button" className="btn-secondary">
Cancelar
</button>
</div>
</form>
</div>
)
} RootLayout
└── Template (si existe)
└── Layout específico
└── Template específico (si existe)
└── Page app/
├── layout.tsx # RootLayout
├── template.tsx # Template global
├── page.tsx # Home page
└── dashboard/
├── layout.tsx # DashboardLayout
├── template.tsx # DashboardTemplate
├── page.tsx # Dashboard home
└── users/
├── layout.tsx # UsersLayout
└── page.tsx # Users page // Layout global - solo lo esencial
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="es">
<body>
<div id="root">
{children}
</div>
</body>
</html>
)
} // Layout de aplicación - navegación principal
import { Navigation } from '../components/Navigation'
export default function AppLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<div className="app-layout">
<Navigation />
<main>{children}</main>
</div>
)
} Con esta guía tienes todo lo necesario para dominar layouts y templates en Next.js. La clave está en entender cuándo usar cada uno y mantener una estructura clara y escalable.