Layouts y Templates en Next.js

Los Layouts y Templates son fundamentales para crear aplicaciones escalables y mantener consistencia visual en Next.js. Te explico todo lo que necesitas saber.

1. Layouts Básicos

Un Layout es un componente que envuelve múltiples páginas y mantiene su estado entre navegaciones. Se define con layout.tsx.

Estructura básica

app/
├── layout.tsx          # Layout raíz (obligatorio)
├── page.tsx           # Página principal
└── dashboard/
    ├── layout.tsx     # Layout específico
    └── page.tsx       # Página del dashboard

Layout raíz

tsx
// 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>
)
}

Layout específico

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>
)
}

2. Separación del Layout y Page

¿Cuál es la diferencia?

  • Layout: Se mantiene entre navegaciones, no se re-renderiza
  • Page: Se re-renderiza en cada navegación
  • Layout envuelve a Page

Ejemplo práctico

tsx
// 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>
)
}
tsx
// 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>
)
}

3. Layouts que Envuelvan Secciones Específicas

Layouts anidados

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

Layout de Marketing

tsx
// 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>
)
}

Layout de Dashboard con Sidebar

tsx
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>
)
}

Layout específico para Analytics

tsx
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>
)
}

4. Templates en Next.js

¿Qué son los Templates?

Los Templates son similares a los layouts, pero se re-renderizan en cada navegación. Útiles cuando necesitas:

  • Animaciones de entrada/salida
  • Resetear estado en cada página
  • Efectos que deben ejecutarse en cada navegación

Template básico

tsx
'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>
)
}

Template con efectos

tsx
'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>
)
}

5. Creación de Templates o Partials

Componentes reutilizables

tsx
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>
)
}

Usando el partial

tsx
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>
)
}

Template de formulario reutilizable

tsx
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>
)
}

6. Jerarquía de Layouts y Templates

Orden de renderizado

RootLayout
  └── Template (si existe)
      └── Layout específico
          └── Template específico (si existe)
              └── Page

Ejemplo completo

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

7. Mejores Prácticas

✅ Hacer

  • Usa layouts para UI que persiste entre páginas
  • Usa templates para animaciones y efectos
  • Mantén los layouts simples y enfocados
  • Crea componentes reutilizables para elementos comunes

❌ Evitar

  • No pongas lógica compleja en layouts
  • No uses templates innecesariamente (son más costosos)
  • No anides demasiados layouts (máximo 3-4 niveles)

Ejemplo de estructura recomendada

tsx
// 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>
)
}
tsx
// 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.