En las arquitecturas tradicionales de React, existía una división: o bien todo el componente se ejecutaba en el cliente, o el servidor realizaba SSR, pero todo el código se enviaba de todos modos al navegador para una hidratación completa.
Next.js 16 y React 19 introducen un modelo dual de componentes revolucionario:
DATABASE_URL, claves de API). El código de los componentes de servidor y sus dependencias nunca llegan al bundle del navegador (Zero Bundle Size).'use client'. Se renderizan en el servidor como HTML inicial, y luego son descargados por el navegador y pasan por la hidratación (Hydration), obteniendo la capacidad de usar hooks (useState, useEffect), APIs del navegador (localStorage, geolocalización) y escuchadores de eventos (onClick, onChange).La directiva 'use client' no es una instrucción para "ejecutar solo en el navegador". Es una declaración de un punto de división de módulos (Network Boundary): todo lo que se importa bajo esta directiva es empaquetado por el bundler en el bundle de JavaScript del cliente.
Veamos el árbol de componentes y el límite de hidratación:
+-------------------------------------------------------------------------+
| MODELO MENTAL DEL LÍMITE 'use client' |
+-------------------------------------------------------------------------+
| ZONA DEL SERVIDOR (runtime de Node.js, acceso a BD, JS cero en el bundle): |
| [Page (RSC)] ---> lee la BD directamente: SELECT * FROM products |
| | |
| +--- [ProductHeader (RSC)] ---> renderizado estático del servidor |
| | |
| ===== LÍMITE 'use client' ============================================= |
| | |
| ZONA DEL CLIENTE (Descargado por el navegador, hidratado, interactivo): |
| +--- [AddToCartButton ('use client')] |
| - usa useState, onClick |
| - se anima y guarda el estado local |
+-------------------------------------------------------------------------+En el servidor se ejecutan todos los RSC. Acceden a las bases de datos y preparan los datos crudos.
El servidor genera el HTML inicial para el navegador y los descriptores Flight para transferir datos al cliente.
Los componentes de cliente también se ejecutan en el servidor para formar el esqueleto visual inicial (SSR).
El navegador da vida solo a las 'islas' de cliente con la directiva 'use client', ignorando el código del servidor.
¿Qué significa exactamente añadir la línea `'use client'` al principio del archivo de un componente?
process.env.STRIPE_SECRET_KEY dentro de Server Components sin riesgo de fuga al navegador.'use client' solo al botón de añadir al carrito o a la barra de búsqueda, manteniendo el 95% de la página como servidor.children: pasar contenido pesado del servidor dentro de una ventana modal interactiva sin convertir el contenido en código de cliente.Los componentes de servidor pueden ser asíncronos (async/await) y no requieren useEffect para las solicitudes:
// ¡Este es un Server Component por defecto!
// ¡Aquí no hay 'use client'!
interface Product {
id: string;
name: string;
price: number;
}
// Solicitud asíncrona directa a una fuente de servidor
async function getProducts(): Promise<Product[]> {
// Acceso directo a la BD o a una API externa
const res = await fetch('https://api.wpskills.dev/v1/products', {
// En Next.js 16, el almacenamiento en caché se configura a través de 'use cache' o parámetros de fetch
next: { revalidate: 60 },
});
return res.json();
}
export default async function ProductsPage() {
const products = await getProducts();
return (
<div className="max-w-5xl mx-auto py-8">
<h1 className="text-2xl font-bold mb-6">Catálogo de Equipos</h1>
<div className="grid grid-cols-1 md:grid-cols-3 gap-6">
{products.map(product => (
<div key={product.id} className="p-4 rounded-xl border border-border bg-bg-surface">
<h3 className="font-semibold text-text-primary">{product.name}</h3>
<p className="text-accent-cyan font-bold mt-2">${product.price}</p>
{/* Botón interactivo del cliente */}
<AddToCartButton productId={product.id} />
</div>
))}
</div>
</div>
);
}Movemos 'use client' estrictamente al nodo hoja interactivo:
'use client'; // OBLIGATORIO: ¡declaramos el límite del cliente!
import React, { useState } from 'react';
export function AddToCartButton({ productId }: { productId: string }) {
const [isAdded, setIsAdded] = useState(false);
const handleAdd = () => {
setIsAdded(true);
// Lógica para añadir al carrito (localStorage o zustand)
console.log(`Producto ${productId} añadido al carrito`);
};
return (
<button
onClick={handleAdd}
className={`mt-4 w-full py-2 px-4 rounded-lg text-sm font-semibold transition-colors ${
isAdded
? 'bg-emerald-500/20 text-emerald-400 border border-emerald-500/30'
: 'bg-accent-cyan text-bg-base hover:opacity-90'
}`}
>
{isAdded ? 'Añadido al carrito ✓' : 'Comprar'}
</button>
);
}Un componente de cliente no puede importar directamente un componente de servidor (import ServerComp from './ServerComp' lo convertiría en cliente).
Solución: ¡Pasar el componente de servidor a través de la prop children!
// 1. Contenedor del cliente (ModalWrapper.tsx):
'use client';
import { useState } from 'react';
export function ModalWrapper({ children }: { children: React.ReactNode }) {
const [isOpen, setIsOpen] = useState(false);
return (
<div>
<button onClick={() => setIsOpen(true)}>Abrir</button>
{isOpen && <div className="modal-backdrop">{children}</div>}
</div>
);
}
// 2. Página del servidor (page.tsx):
// ¡ServerComponent se renderiza en el servidor y se pasa como children ya preparado!
export default function Page() {
return (
<ModalWrapper>
{/* ¡HeavyServerTable se ejecutará EN EL SERVIDOR y no aumentará el tamaño del bundle! */}
<HeavyServerTable />
</ModalWrapper>
);
}¿Por qué pasar un componente de servidor a través de la prop children a un componente de cliente (<ClientModal><ServerList /></ClientModal>) mantiene su ejecución en el servidor?
Si se coloca 'use client' en todo el archivo page.tsx, toda la página y todos sus módulos importados descendientes se convertirán en componentes de cliente, privándote de las ventajas de los Server Components.
Solución: Mantén las páginas y los layouts como componentes de servidor (Server Components), y baja 'use client' en el árbol a botones interactivos específicos, formularios y widgets (Move Client Boundary Down).
Un componente de servidor pasa props a un componente de cliente a través del protocolo de serialización de red Flight. No se pueden pasar funciones, instancias de clases personalizadas o referencias cíclicas:
// ERROR: ¡las funciones no se pueden serializar a través del límite Server -> Client!
<ClientComponent onClick={() => console.log('server')} />Las funciones manejadoras deben declararse dentro del propio componente de cliente o pasarse a través de Server Actions ('use server').