Una de las características más importantes del App Router de Next.js es que los componentes son Server Components por defecto. Esto permite obtener datos directamente desde el servidor sin necesidad de enviar al navegador código JavaScript adicional para realizar la petición.
Esto resulta especialmente útil cuando trabajamos con APIs, bases de datos o servicios externos.
1. Crear un Server Component
Podemos crear una página dentro de app y convertirla en un componente asíncrono:
export default async function ProductosPage() {
const respuesta = await fetch(
"https://fakestoreapi.com/products"
);
const productos = await respuesta.json();
return (
<main>
<h1>Productos</h1>
{productos.map((producto) => (
<article key={producto.id}>
<h2>{producto.title}</h2>
<p>Precio: ${producto.price}</p>
</article>
))}
</main>
);
}
Observa que podemos utilizar async/await directamente dentro del componente. La petición se ejecuta en el servidor antes de enviar el contenido al navegador.
2. ¿Por qué es diferente a React tradicional?
En una aplicación React tradicional podríamos utilizar useEffect() y useState() para obtener información:
useEffect(() => {
fetch("/api/productos")
.then(res => res.json())
.then(data => setProductos(data));
}, []);
Con Server Components, muchas veces no necesitamos este código. Los datos pueden obtenerse directamente durante el renderizado del servidor.
Esto puede reducir la cantidad de JavaScript que recibe el navegador y mejorar el rendimiento inicial.
3. Consumir una API propia
También podemos crear una función independiente para organizar mejor nuestro código:
async function obtenerProductos() {
const respuesta = await fetch(
"https://fakestoreapi.com/products"
);
if (!respuesta.ok) {
throw new Error("No se pudieron obtener los productos");
}
return respuesta.json();
}
export default async function ProductosPage() {
const productos = await obtenerProductos();
return (
<section>
<h1>Catálogo</h1>
{productos.map((producto) => (
<div key={producto.id}>
{producto.title}
</div>
))}
</section>
);
}
Separar la lógica de obtención de datos facilita mantener y reutilizar el código.
4. Controlar la caché
Next.js permite controlar cómo se gestionan las peticiones. Por ejemplo:
const respuesta = await fetch(
"https://api.ejemplo.com/productos",
{
cache: "no-store"
}
);
Con cache: "no-store", la información se solicita nuevamente cuando corresponde, algo útil para datos que necesitan estar actualizados.
También podemos utilizar opciones de revalidación:
const respuesta = await fetch(
"https://api.ejemplo.com/productos",
{
next: {
revalidate: 60
}
}
);
Esto permite trabajar con datos que pueden actualizarse periódicamente sin solicitar siempre la información desde cero.
5. ¿Cuándo usar Client Components?
Los Server Components son ideales para obtener y mostrar datos, pero no sustituyen completamente a los Client Components.
Si necesitamos useState, useEffect, eventos del navegador, interacción directa con el usuario o APIs como window, normalmente debemos utilizar:
"use client";
Una buena arquitectura consiste en obtener los datos en el servidor y utilizar componentes cliente únicamente para las partes interactivas.
Conclusión
Consumir datos desde Server Components simplifica muchas aplicaciones de Next.js. Podemos realizar peticiones con async/await, acceder a APIs y preparar la información directamente en el servidor, reduciendo trabajo innecesario en el navegador.
La clave está en aprovechar Server Components para la obtención de datos y reservar Client Components para la interactividad.

