Componentes Servidor y Cliente
Entiende React Server Components y cuándo usar componentes cliente
React Server Components (RSC)
Next.js usa React Server Components por defecto en el App Router. Los Server Components se renderizan en el servidor, reducen el JavaScript del lado del cliente y pueden acceder directamente a recursos backend como bases de datos y sistemas de archivos.
🖥️ Componentes Servidor (Por Defecto)
- ✅ Obtener datos directamente (sin useEffect)
- ✅ Acceder a recursos backend (BD, filesystem)
- ✅ Mantener información sensible en el servidor (API keys)
- ✅ Reducir tamaño del bundle del cliente
- ❌ No pueden usar hooks (useState, useEffect)
- ❌ No pueden usar APIs del navegador
- ❌ No pueden agregar event listeners
💻 Componentes Cliente ('use client')
- ✅ Usar hooks de React (useState, useEffect)
- ✅ Agregar event listeners (onClick, onChange)
- ✅ Acceder a APIs del navegador (localStorage, window)
- ✅ Usar librerías de hooks de terceros
- ❌ No pueden ser funciones async
- ❌ Aumentan el tamaño del bundle del cliente
Ejemplo de Componente Servidor
Los Server Components pueden ser async, obtener datos directamente y mantener secretos en el servidor mientras retornan solo HTML al cliente.
// app/users/page.tsx - Componente Servidor (por defecto)
// No se necesita directiva 'use client'
import { db } from '@/lib/database';
// Este componente se ejecuta solo en el servidor
export default async function UsersPage() {
// Acceso directo a base de datos - ¡no se necesita API!
const users = await db.user.findMany();
// Async/await funciona directamente en el componente
const stats = await fetch('https://api.example.com/stats', {
headers: {
// Seguro de usar - nunca se envía al cliente
Authorization: `Bearer ${process.env.SECRET_API_KEY}`,
},
}).then(res => res.json());
return (
<div>
<h1>Usuarios ({stats.total})</h1>
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</div>
);
}
// ¡Cero JavaScript del lado del cliente para este componente!
Ejemplo de Componente Cliente
Los Client Components optan por APIs del navegador y hooks, por lo que son ideales para UI interactiva y comportamiento con estado.
'use client'; // Esta directiva lo hace un Componente Cliente
import { useState, useEffect } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
const [mounted, setMounted] = useState(false);
// useEffect funciona en Componentes Cliente
useEffect(() => {
setMounted(true);
// Acceder a APIs del navegador
const saved = localStorage.getItem('count');
if (saved) setCount(parseInt(saved));
}, []);
const increment = () => {
setCount(c => c + 1);
localStorage.setItem('count', String(count + 1));
};
if (!mounted) return null; // Evitar desajuste de hidratación
return (
<button onClick={increment}>
Contador: {count}
</button>
);
}
Patrones de Composición
Prefiere envolver Server Components con un pequeño Client Component para mantener la mayoría del árbol renderizado en el servidor y evitar inflar el bundle del cliente.
// ✅ CORRECTO: Pasar Server Components como children
// app/dashboard/page.tsx (Componente Servidor)
import ClientWrapper from './ClientWrapper';
import ServerData from './ServerData';
export default async function Dashboard() {
return (
<ClientWrapper>
{/* ServerData permanece como Server Component */}
<ServerData />
</ClientWrapper>
);
}
// ClientWrapper.tsx
'use client';
export default function ClientWrapper({
children
}: {
children: React.ReactNode
}) {
const [isOpen, setIsOpen] = useState(false);
return (
<div>
<button onClick={() => setIsOpen(!isOpen)}>Toggle</button>
{isOpen && children}
</div>
);
}
// ❌ INCORRECTO: Importar Server Component en Client Component
'use client';
import ServerComponent from './ServerComponent'; // ¡Esto se convierte en Client Component!
Cuándo Usar Cada Uno
| Caso de Uso | Tipo de Componente |
|---|---|
| Obtener datos | 🖥️ Servidor |
| Acceder a recursos backend | 🖥️ Servidor |
| Mantener datos sensibles seguros | 🖥️ Servidor |
| Agregar interactividad (onClick, onChange) | 💻 Cliente |
| Usar estado (useState, useReducer) | 💻 Cliente |
| Usar efectos de ciclo de vida (useEffect) | 💻 Cliente |
| Usar APIs solo del navegador | 💻 Cliente |
| Usar React Context | 💻 Cliente |
Mezclar Componentes
Mantén la interactividad del cliente en las hojas para que los componentes pesados en datos permanezcan renderizados en el servidor.
// Mejor Práctica: Mantener Componentes Cliente en las hojas
// ❌ MAL: Cliente Component grande envolviendo todo
'use client';
export default function Page() {
const [filter, setFilter] = useState('');
return (
<div>
<input onChange={e => setFilter(e.target.value)} />
<DataTable filter={filter} /> {/* Ahora forzado a ser cliente */}
</div>
);
}
// ✅ BIEN: Pequeño Client Component solo para interactividad
// SearchFilter.tsx
'use client';
export function SearchFilter({ onFilter }: { onFilter: (v: string) => void }) {
return <input onChange={e => onFilter(e.target.value)} />;
}
// page.tsx (Componente Servidor)
import { SearchFilter } from './SearchFilter';
import { DataTable } from './DataTable'; // Permanece Server Component
export default async function Page() {
const data = await fetchData();
return (
<div>
<SearchFilter onFilter={handleFilter} />
<DataTable data={data} />
</div>
);
}
Librerías de Terceros
Muchas librerías de UI dependen de hooks, así que envuélvelas en un pequeño Client Component y pasa datos desde el servidor.
// Muchas librerías usan hooks y necesitan 'use client'
// Crear componentes wrapper para componentes de terceros
// components/ChartWrapper.tsx
'use client';
import { LineChart } from 'some-chart-library';
export function ChartWrapper({ data }: { data: number[] }) {
return <LineChart data={data} />;
}
// Usar en Componente Servidor
import { ChartWrapper } from './ChartWrapper';
export default async function Dashboard() {
const data = await fetchChartData(); // Obtención del lado del servidor
return <ChartWrapper data={data} />; // Renderizado del lado del cliente
}
⚡ Puntos Clave
- • Por defecto usa Server Components - solo usa 'use client' cuando sea necesario
- • Mantén los Client Components pequeños y en los nodos hoja
- • Pasa Server Components como children a Client Components
- • No importes Server Components en Client Components
- • Envuelve librerías de terceros que necesiten hooks