Integración de componentes de cliente y servidor en Next.js

Published on
Integración de componentes de cliente y servidor en Next.js

Integración de Componentes de Servidor dentro de Componentes de Cliente

Introducción

Los Componentes de Cliente en Next.js permiten interfaces de usuario interactivas que son pre-renderizadas en el servidor pero ejecutan JavaScript del lado del cliente en el navegador. Ofrecen interactividad a través de estado, efectos y oyentes de eventos, permitiendo actualizaciones dinámicas de la UI y retroalimentación inmediata al usuario.

Para utilizar Componentes de Cliente, la "use client" directiveExternal Link de React se coloca en la parte superior de un archivo, estableciendo un límite entre los componentes del módulo de servidor y cliente.

Los módulos importados, incluyendo los componentes hijos, se tratan como parte del paquete del cliente (componentes de cliente).

Nota: Una vez que se ha utilizado "use client" en el componente padre, no necesita definirse en cada componente hijo que necesite renderizarse en el cliente.

Entendiendo el Desafío

Los Componentes de Servidor siempre se renderizan primero durante una solicitud, por lo tanto, importar directamente Componentes de Servidor en Componentes de Cliente no es compatible porque requeriría solicitudes adicionales al servidor.

Sin embargo, hay un método para integrar componentes de servidor dentro de componentes de cliente. En su lugar, podemos pasar Componentes de Servidor como props a Componentes de Cliente.

Veamos un ejemplo:

// Consejo: Este será un componente cliente hijo y no necesita la directiva "use client" en el nivel superior
import { useState } from "react";

export default function Counter() {
  const [count, setCount] = useState(0);

  return <button onClick={() => setCount((prev) => prev + 1)}>Contar: {count}</button>;
}
app/components/counter.tsx

Usamos los hijos de React como props para crear un "espacio" en nuestro Componente de Cliente. Esto nos permitirá renderizar un hijo que es un componente de servidor.

A continuación, <ClientComponent> acepta un prop de hijos:

"use client";

import Counter from "./counter";

export default function ClientComponent({ children }: { children: React.ReactNode }) {
  return (
    <>
      <h1>Componente Cliente</h1>
      <Counter />
      {children}
    </>
  );
}
app/components/client-component.tsx

Luego podemos tener un componente de servidor como este:

import React from "react";

function ServerTitle() {
  return <div>¡Contempla!, Soy un título renderizado en el servidor!</div>;
}

export default ServerTitle;
app/components/server-title.tsx

<ClientComponent> no sabe que los hijos eventualmente serán llenados por el resultado de un Componente de Servidor. La única responsabilidad de <ClientComponent> es decidir dónde los hijos serán colocados eventualmente.

Todo junto se verá así:

import ClientComponent from "./components/client-component";
import ServerTitle from "./components/server-title"; // Componente de servidor

export default async function Home() {
  return (
    <ClientComponent>
      <ServerTitle />
    </ClientComponent>
  );
}
app/page.tsx

Este patrón puede ser útil si tenemos componentes que sabemos que pueden ser renderizados en el servidor.

Por ejemplo, envolviendo la aplicación en un proveedor de contexto o si queremos incluir algo común como temas en nuestra aplicación:

"use client";

import { ThemeProvider as NextThemesProvider } from "next-themes";

function ThemeProvider({ children }: { children: React.ReactNode }) {
  return (
    <NextThemesProvider attribute="class" defaultTheme="system" enableSystem>
      {children}
    </NextThemesProvider>
  );
}

export default ThemeProvider;
app/components/theme-provider.tsx
import ThemeProvider from "./components/theme-provider";

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="en" suppressHydrationWarning>
      <body>
        <ThemeProvider>{children}</ThemeProvider>
      </body>
    </html>
  );
}
app/layout.tsx

Próximamente: guía de temas

Conclusión

No todos los archivos requieren la directiva "use client". Todo lo que se importe después de establecer ese límite se considerará un componente del cliente. Sin embargo, todavía es posible pre-renderizar "children" en el servidor.