logo blog voltadev
Server & Client Components in Next JS

Server & Client Components in Next JS

- views - 01-29-2024

Bienvenidos al Blog 😄

En este post, exploraremos un resumen de mis apuntes sobre Server Components y Client Components en el framework de Next.js. También veremos ejemplos de código trabajando con la API de Pokémon.

Server Components & Client Components en Next.js

Imaginemos un árbol donde el servidor crea la estructura completa, pero solo algunas hojas se generan en el lado del cliente.

Server Components

Los Server Components se renderizan en el servidor y solo una vez. Su propósito principal es realizar la lógica de renderizado en el lado del servidor (SSR) y generar contenido que no cambia en cada solicitud.

Están diseñados para manejar la lógica del servidor y generar partes estáticas de la página que no dependen de la interacción del usuario.


Client Components

Los Client Components se renderizan en el navegador y pueden cambiar su estado y volver a renderizarse en respuesta a las interacciones del usuario. Están destinados a la lógica del lado del cliente y permiten actualizaciones interactivas en la interfaz de usuario.


En resumen, los Server Components se renderizan una vez en el servidor y generan contenido estático, mientras que los Client Components pueden renderizarse varias veces en el navegador en respuesta a eventos del usuario. Es importante entender la distinción y utilizar cada tipo de componente según sus propias características y propósito.


Ahora, construyamos algo dinámico, contenido generado por el servidor bajo demanda. Esto significa que, en tiempo de ejecución, mientras nuestra aplicación está en funcionamiento, realizaremos una construcción dinámica. Se solicita un ID específico, y lo procesaremos en el lado del servidor. Lo construiremos basándonos en la solicitud del usuario. Si permitimos que la persona escriba libremente la URL, puede causar ciertos problemas.


Eso significa que la persona puede enviar cualquier cosa; pueden enviar un ID a través del argumento de la URL. Para manejar esto, podemos gestionar errores. Ahora, creemos contenido generado por el servidor, considerando que una parte significativa del contenido es estático. Si tenemos componentes del servidor y no agregamos ningún tipo de interactividad, terminamos creando contenido estático que no cambiará físicamente; este es HTML estático.


👀 Analizaremos este código page.tsx

import { notFound } from "next/navigation";
import { Pokemon } from "@/elements";
import { Metadata } from "next";
import Image from "next/image";

interface Props {
  params: { name: string };
}

//this only executing in build time
export async function generateStaticParams() {
  const static151Pokemons = Array.from({ length: 151 }).map(
    (v, i) => `${i + 1}`
  );

  return static151Pokemons.map((name) => ({
    name: name,
  }));
}

export async function generateMetadata({ params }: Props): Promise<Metadata> {
  try {
    const { id, name } = await getPokemon(params.name);

    return {
      title: `#${id} - ${name}`,
      description: `Page of pokémon`,
    };
  } catch (error) {
    return {
      title: `Page of Pokémon`,
      description: `Not found`,
    };
  }
} //create from the side of server

//keep in cache
const getPokemon = async (name: string): Promise<Pokemon> => {
  try {
    const pokemon = await fetch(`https://pokeapi.co/api/v2/pokemon/${name}`, {
      //cache: "force-cache",
      next: {
        revalidate: 60 * 60 * 2 * 2,
      },
    }).then((resp) => resp.json());
    console.log("load", pokemon);

    return pokemon;
  } catch (error) {
    notFound();
  }
};

Para gestionar errores cuando el usuario busca una URL que no existe, podemos crear un archivo 'notFound' dentro de la carpeta del componente. De esta manera, el manejo de este error se realizará a nivel local del componente en lugar de manera global.


not-found-tsx

not-found.tsx

import Link from "next/link";

export default function NotFound() {
  return (
    <main className="h-screen w-full flex flex-col justify-center items-center bg-[#1A2238]">
      <h1 className="text-9xl font-extrabold text-white tracking-widest">
        404
      </h1>
      <div className="bg-[#FF6A3D] px-2 text-sm rounded rotate-12 absolute">
        Pokémon Not Found
      </div>
      <button className="mt-5">
        <div className="relative inline-block text-sm font-medium text-[#FF6A3D] group active:text-orange-500 focus:outline-none focus:ring">
          <span className="absolute inset-0 transition-transform translate-x-0.5 translate-y-0.5 bg-[#FF6A3D] group-hover:translate-y-0 group-hover:translate-x-0"></span>

          <span className="relative block px-8 py-3 bg-[#1A2238] border border-current">
            <Link href="/dashboard/elements">See list of pokémons</Link>
          </span>
        </div>
      </button>
    </main>
  );
}

Resumen del Código page.tsx

  • Importación de Módulos

Importa funciones y componentes de Next.js, incluyendo la función notFound para manejar páginas no encontradas, y componentes como Pokemon y Metadata.

import { notFound } from "next/navigation";
import { Pokemon } from "@/elements";
import { Metadata } from "next";
import Image from "next/image";
  • Definición de Interfaz

Define una interfaz Props con parámetros de ruta.

interface Props {
  params: { name: string };
}
  • Generación de Parámetros Estáticos

Define una función generateStaticParams que genera un array de nombres de Pokémon estáticos para construir rutas.

export async function generateStaticParams() {
  // Crea nombres estáticos de Pokémon
  const static151Pokemons = Array.from({ length: 151 }).map(
    (v, i) => `${i + 1}`
  );

  // Retorna un array de objetos con nombres
  return static151Pokemons.map((name) => ({
    name: name,
  }));
}
Generación de Metadatos

Define una función generateMetadata que obtiene detalles de un Pokémon y crea metadatos para la página.

export async function generateMetadata({ params }: Props): Promise<Metadata> {
  try {
    // Obtiene detalles del Pokémon por su nombre
    const { id, name } = await getPokemon(params.name);

    // Retorna metadatos con título y descripción
    return {
      title: `#${id} - ${name}`,
      description: `Page of Pokémon`,
    };
  } catch (error) {
    // En caso de error, retorna metadatos para página no encontrada
    return {
      title: `Page of Pokémon`,
      description: `Not found`,
    };
  }
}
Función de Obtención de Pokémon

Define una función getPokemon que realiza una solicitud a la API de Pokémon y devuelve los detalles del Pokémon, con manejo de caché y revalidación.

const getPokemon = async (name: string): Promise<Pokemon> => {
  try {
    // Realiza solicitud a la API de Pokémon
    const pokemon = await fetch(`https://pokeapi.co/api/v2/pokemon/${name}`, {
      next: {
        revalidate: 60 * 60 * 2 * 2,
      },
    }).then((resp) => resp.json());

    // Registra en la consola y retorna los detalles del Pokémon
    console.log("load", pokemon);
    return pokemon;
  } catch (error) {
    // En caso de error, maneja página no encontrada
    notFound();
  }
};

En resumen este código en Next.js 14 se centra en la generación de páginas estáticas y metadatos para una lista de Pokémon, utilizando funciones asincrónicas y manejo de errores. La función getPokemon también implementa caché y revalidación para mejorar la eficiencia de la carga de datos.


Example blog server and clinet component


El ejemplo anterior es un blog con 2 publicaciones, donde puedes ver según el color cual es es renderizado desde el lado del servidor o cliente.


¡Muchas gracias por tu lectura dejo el link de cafecito apoyo voluntario, tu contribución me motiva a seguir creando contenido de calidad. ¡Muchas gracias por tu apoyo!


Buy Me a Coffee at ko-fi.com