Server Components en Next.js explicados sin humo

Coding on a laptop outdoors, showcasing a rooftop urban lifestyle in Surat, India.
Foto de Meet Patel en Pexels

En los últimos meses los Server Components se han convertido en una de las funcionalidades más comentadas de Next.js. A diferencia de los componentes tradicionales de React, que siempre se ejecutan en el navegador, los Server Components se renderizan en el servidor y devuelven HTML listo para enviarse al cliente. En este artículo vamos a desmitificar su funcionamiento, ver cómo declararlos en un proyecto Next.js y explorar sus ventajas y limitaciones, todo con ejemplos claros y sin rodeos.

¿Qué son los Server Components?

Un Server Component es simplemente una función de React que se ejecuta en el entorno Node.js del servidor. No tiene acceso al window, a los hooks de estado (useState, useEffect) ni a ninguna API del navegador. Su único objetivo es obtener datos, procesarlos y devolver JSX que se convierte en HTML estático. Gracias a esto, el HTML generado es totalmente compatible con SEO y se entrega al cliente sin necesidad de hidratarlo.

Cómo se declaran en Next.js

En Next.js 13, la distinción entre componentes de servidor y de cliente se hace a través del nombre del archivo. Si el nombre termina en .server.jsx (o .server.tsx) el componente se trata como Server Component. Por el contrario, si añadimos la directiva "use client" al principio del archivo, se fuerza a que sea un Client Component. Esta convención permite mezclar ambos tipos dentro del mismo árbol de componentes sin romper la arquitectura.

/ app/components/MessageList.server.jsx /
import { fetchMessages } from '@/lib/api';

export default async function MessageList() {
  const messages = await fetchMessages();
  return (
    <ul>
      {messages.map(msg => (
        <li key={msg.id}>{msg.text}</li>
      ))}
    </ul>
  );
}

Flujo de datos y renderizado

Los Server Components pueden ejecutar cualquier código asíncrono antes de devolver el JSX. En el ejemplo anterior, fetchMessages es una llamada a una API que se realiza en el servidor. Cuando la promesa se resuelve, Next.js vuelve a renderizar el componente y envía el HTML resultante al cliente. Si el componente está dentro de una ruta del app router, el framework puede hacer streaming del HTML, enviando fragmentos a medida que se generan y mejorando la percepción de velocidad.

Ventajas y limitaciones

  • SEO friendly: el HTML se genera en el servidor, por lo que los rastreadores lo ven completo.
  • Menor carga de JavaScript: los Server Components no envían código cliente, reduciendo el bundle.
  • Acceso a recursos del servidor: bases de datos, filesystem, variables de entorno, etc.
  • Sin estado local: no puedes usar useState ni useEffect dentro de ellos.
  • Mayor complejidad en el árbol: mezclar Server y Client Components requiere planificar bien la frontera.

Ejemplo práctico: lista de mensajes con interacción

Supongamos que queremos mostrar una lista de mensajes y, al hacer clic en uno, abrir un modal que permita responder. La lista se puede renderizar como Server Component, mientras que el modal será un Client Component porque necesita gestionar estado y eventos.

/ app/components/MessageModal.client.jsx /
"use client";
import { useState } from 'react';

export default function MessageModal({ message }) {
  const [reply, setReply] = useState('');

  const handleSend = async () => {
    await fetch('/api/reply', {
      method: 'POST',
      body: JSON.stringify({ id: message.id, text: reply })
    });
    setReply('');
  };

  return (
    <div className="modal">
      <h2>Responder a: {message.text}</h2>
      <textarea value={reply} onChange={e => setReply(e.target.value)} />
      <button onClick={handleSend}>Enviar</button>
    </div>
  );
}

En la página principal podemos combinar ambos componentes:

/ app/page.jsx /
import MessageList from '@/components/MessageList.server';
import MessageModal from '@/components/MessageModal.client';
import { useState } from 'react';

export default function HomePage() {
  const [selected, setSelected] = useState(null);

  return (
    <>
      <MessageList onSelect={setSelected} />
      {selected && <MessageModal message={selected} onClose={() => setSelected(null)} />}
    </>
  );
}

Consejos para usar Server Components en producción

1. Define bien la frontera: coloca la lógica de datos y renderizado estático en Server Components y reserva los Client Components para interactividad. 2. Aprovecha el streaming: usa await estratégicamente para que partes del HTML se envíen tan pronto como estén listas. 3. Controla el tamaño del bundle: evita importar librerías pesadas en Server Components que no se usarán en el cliente. 4. Prueba el rendimiento: usa herramientas como Lighthouse para comparar la carga antes y después de migrar a Server Components.

Conclusión

Los Server Components ofrecen una forma poderosa de combinar lo mejor del renderizado del servidor con la flexibilidad de React. Al generar HTML en el servidor, mejoramos el SEO y reducimos la carga de JavaScript, mientras que los Client Components siguen proporcionando la interactividad necesaria. La clave está en entender cuándo y cómo dividir tu UI entre ambos tipos. Con la práctica, podrás crear aplicaciones Next.js más rápidas, ligeras y mantenibles, sin necesidad de “humo” adicional.