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
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émonDefine 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.
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!
