Escuela Frontend
Next.js

Data Fetching en Next.js Usando getServerSideProps y getStaticProps

Alejandro Ñáñez
Autor
Alejandro Ñáñez

En este post exploraremos cómo consumir datos de una API REST en una aplicación Next.js, además de cómo y cuándo usar Server Side Rendering (SSR) o Static Site Generation (SSG).

Next.js se ha convertido en uno de los frameworks de React más populares en los últimos años por su habilidad de crear aplicaciones híbridas, lo que significa que podremos decidir el método de pre-renderizado que más se ajuste a nuestras necesidades. Además, podremos escoger qué método utilizar en cada una de las rutas de nuestra aplicación, es decir que podremos usar Server Side Rendering (SSR) para una ruta /ssr y Static Site Generation (SSG) para una ruta /ssg.

El hecho de que puedas escoger cualquiera de los dos métodos en cada una de las rutas de tu aplicación, hace de Next.js una alternativa maravillosa y versátil para tu próximo proyecto.

¿Qué aprenderás?

En este artículo le darás una mirada profunda a cómo consumir datos de un API REST pública para construir dos páginas en una aplicación Next.js. La primera página, usará Server Side Rendering (SSR) y la segunda Static Site Generation (SSG).

Al finalizar este artículo, entenderás:

  • Cómo funcionan cada uno de los métodos de pre-renderizado en Next.js
  • Qué método utilizar dependiendo de las circunstancias.

Pre-requisitos

En este artículo usaremos fetch lo cual nos permitirá consumir una API REST pública, para luego construir dos páginas diferentes, una que use Server Side Rendering (SSR) y otra Static Site Generation. Para sacarle el mayor provecho a este artículo, te recomiendo los siguientes enlaces:

Outline

  • Crearemos una aplicación Next.js desde cero utilizando create-next-app
  • Crearemos las dos rutas necesarias
    • /ssg Esta ruta usará Server Side Rendering (SSR)
    • /ssr Esta ruta usará Static Site Generation (SSG)
  • Consumiremos los datos desde {JSON}Placeholder
    • Usaremos fetch
    • Manejo básico de errores
  • Configuraremos cada una de las rutas para usar los métodos respectivos (SSG y SSR)

Crear la aplicación

Descarguemos e instalemos el proyecto localmente

# Clonemos el repositorio
git clone https://github.com/alejandronanez/nextjs-data-fetching
# Entremos al proyecto
cd nextjs-data-fetching
# Instalemos las dependencias
npm install

Este proyecto fue creado con create-next-app que es un Command Line Interface (CLI) creado y mantenido por el equipo de Next.js, lo cual nos permite crear proyectos de Next.js utilizando diferentes templates. El template que utilizamos, es el template por defecto, es una aplicación de Next.js que utiliza JavaScript. En este commit, pueden ver la estructura de archivos generada después de correr este comando

npx create-next-app nextjs-data-fetching

Esta es la estructura de archivos con la que empezamos:

├── README.md
├── package-lock.json
├── package.json
├── pages
│ ├── _app.js
│ ├── api
│ │ └── hello.js
│ └── index.js
├── public
│ ├── favicon.ico
│ └── vercel.svg
└── styles
├── Home.module.css
└── globals.css

Antes de continuar, vamos a borrar los archivos que no sean necesarios para que podamos enfocarnos solamente en nuestro ejercicio.

  1. Borremos la carpeta /pages/api/
  2. Borremos el archivo /pages/_app.js
  3. Borremos la carpeta /styles/
  4. Borremos el archivo /pages/index.js 

Esta deberá ser nuestra nueva estructura de archivos:

├── README.md
├── package-lock.json
├── package.json
├── pages
└── public
├── favicon.ico
└── vercel.svg

Creando las dos rutas

Lo que vamos a hacer a continuación, es crear las dos rutas, una para mostrar cómo funciona Server Side Rendering (SSR) /ssr y otra para mostrar cómo funciona Static Site Generation (SSG) /ssg.

Para crear estas rutas, lo que debemos hacer es crear dos archivos dentro de la carpeta pages/, cada uno de los archivos tendrá el nombre de la ruta.

  • /pages/ssr.js
  • /pages/ssg.js

Así debería verse nuestra estructura de archivos después de crear las rutas

# Omitimos otros archivos por claridad
├── pages
│ ├── ssg.js
│ └── ssr.js
# Omitimos otros archivos por claridad

Ahora, corremos el siguiente comando

npm run dev

Este comando se encargará de iniciar nuestra aplicación Next.js en la dirección http://localhost:3000. Si visitamos las rutas http://localhost:3000/ssrhttp://localhost:3000/ssg vamos a obtener un error 404.

Esto ocurre porque no basta con que tengamos los archivos ssg.js y ssr.js para ver algo en la página.

Lo que debemos hacer a continuación es exportar un componente con un export default desde cada uno de esos archivos para deshacernos de esos molestos errores 404. Escribamos el siguiente código en nuestras páginas

// /pages/ssr.js
function SSR() {
return (
<div>SSR</div>
)
}
export default SSR;
// /pages/ssg.js
function SSG() {
return (
<div>SSG</div>
)
}
export default SSG;

Ahora, visitemos los siguientes urls y constatemos que estamos renderizando las páginas que acabamos de crear

Consumiendo los datos desde JSON Placeholder

Tomemos un pequeño desvío. Ahora vamos a escribir el código necesario para traer los datos desde JSON Placeholder API. Cuando terminemos, vamos a utilizar el resultado en las páginas que acabamos de crear en el paso anterior.

Empecemos creando el archivo /data/api.js, en este archivo vamos a incluir toda la lógica encargada de traer datos de la API; vamos a crear una función que se encargue de traer una foto aleatoria desde el siguiente endpoint, donde :photoId va a ser un número entre 1 y 10 gracias a la función getRandomInt

// Grabbed from:
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/random#getting_a_random_integer_between_two_values
function getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min) + min);
}
export async function getRandomPhoto() {
const randomPhotoId = getRandomInt(1, 10);
const photo = await fetch(
`https://jsonplaceholder.typicode.com/photos/${randomPhotoId}`
).then((response) => response.json());
return photo;
}

Una parte importante de este archivo, es que la única función que exportamos es getRandomPhoto porque es la función que vamos a usar en nuestras páginas para traer datos. Al llamar la función getRandomPhoto, vamos a obtener un objeto con la siguiente forma:

// https://jsonplaceholder.typicode.com/photos/2
{
"albumId": 1,
"id": 2,
"title": "reprehenderit est deserunt velit ipsam",
"url": "https://via.placeholder.com/600/771796",
"thumbnailUrl": "https://via.placeholder.com/150/771796"
}

Nota: Los datos van a cambiar dependiendo del photoId y es posible (10 a 1 de probabilidad) que cada vez que llamemos el método, tengamos datos diferentes.

Manejo de errores

Hasta ahora, nuestra función getRandomPhoto es muy optimista, siempre va a retornar datos, a menos que ocurra un error. Hagamos esta función más robusta.

Cómo estamos utilizando la función nativa fetch, tenemos que hacer un poco más de trabajo para manejar nuestros errores porque para fetch, una respuesta con código >=400 <=500 es una respuesta válida. Afortunadamente, el objeto que obtenemos después de llamar fetch tiene una propiedad (response.ok) que nos indica si la respuesta estuvo entre el rango de 200-299, en este punto, podemos asumir que si obtuvimos una respuesta entre 200-299 el backend nos devolvió algo válido. Veamos el código.

export async function getRandomPhoto() {
const randomPhotoId = getRandomInt(1, 10);
let photo;
try {
photo = await fetch(
`https://jsonplaceholder.typicode.com/photos/${randomPhotoId}`
).then((response) => {
/**
* We consider any response not in the ranges of 200-299 as an
* invalid response.
* That covers 4xx errors and 5xx errors.
*/
if (!response.ok) {
throw new Error("Something went wrong with the request");
}
return response.json();
});
} catch (e) {
/**
* We'll get to this block if:
* - Response was NOT OK.
* - We couldn't complete the request
*
* We should log whatever is coming from `e` to our
* reporting/alerting system (see Sentry.io)
*/
console.log("...logging error to our system...");
throw e;
}
return photo;
}

Qué acabamos de hacer:

  • Tratar cualquier respuesta del backend que no esté entre el rango de 200-299 como un error
  • Loggeamos en la consola los errores. Recuerda que idealmente debes enviarlos a alguna parte (por ejemplo, a Sentry) no solo imprimirlos en consola.

Nota: No es suficiente con manejar los errores en la capa de la API de nuestra aplicación, también debemos manejarlos en los métodos de data fetching de Next.js. Haremos eso a continuación cuando unamos getRandomPhoto con Next.js!

Configurar nuestras rutas para usar SSR y SSG

SSR (Server Side Rendering)

En nuestro archivo /pages/ssr.js, vamos a exportar una función asíncronica llamada getServerSideProps. Esta es la función encargada de traer los datos del API y posteriormente pasarlos a React a través de props para renderizar la página. Esta función le indica a Next.js que esta página utilizará SSR (Server Side Rendering), lo cual significa que vamos a ejecutar esa función cada vez que visitemos la ruta /ssr. La función getServerSideProps puede devolver un objeto con 3 llaves:

  • props son los datos que vamos a enviarle a React para ser utilizados en nuestros componentes.
  • notFound es un booleano que le indica a Next.js si encontramos o no la página.
  • redirect utilizado para hacer redirecciones. Esto está fuera del alcance de este tutorial, pero pueden encontrar más información en la documentación oficial
function SSR({photo}) {
return (
<div>
<h1>{photo.title}</h1>
<img src={photo.url}/>
</div>
)
}
export async function getServerSideProps() {
let photo;
try {
photo = await getRandomPhoto();
} catch (e) {
/**
* If something goes wrong, we return a 404 page
*/
return {
notFound: true,
};
}
if (!photo) {
/**
* If we don't get a `photo` back, we return a 404 page
*/
return {
notFound: true,
};
}
return {
props: {
photo,
},
};
}

Este es el resultado final

SSG (Static Site Generation)

Ahora, vamos a hacer el mismo procedimiento en la ruta /pages/ssg.js. Vamos a exportar una función asíncronica llamada getStaticProps. Esta es la función encargada de traer los datos del API y posteriormente pasarlos a React a través de props para renderizar la página. Esta función le indica a Next.js que esta página utilizará SSG (Static Site Generation), esto significa que vamos a ejecutar esa función una sola vez, cuando hagamos el "build" de nuestra aplicación. La función getStaticProps puede devolver un objeto con 3 llaves:

  • props son los datos que vamos a enviarle a React para ser utilizados en nuestros componentes.
  • notFound es un booleano que le indica a Next.js si encontramos o no la página.
  • revalidate utilizado para regenerar una página después de cierto tiempo. Esto está fuera del alcance de este tutorial, pero pueden encontrar más información en la documentación oficial
import { getRandomPhoto } from "../data/api";
function SSG({ photo }) {
return (
<div>
<h1>{photo.title}</h1>
<img src={photo.url} />
</div>
);
}
export async function getStaticProps() {
let photo;
try {
photo = await getRandomPhoto();
} catch (e) {
/**
* If something goes wrong, we return a 404 page
*/
return {
notFound: true,
};
}
if (!photo) {
/**
* If we don't get a `photo` back, we return a 404 page
*/
return {
notFound: true,
};``
}
return {
props: {
photo,
},
};
}
export default SSG;

Nota: Mientras corramos nuestra aplicación en modo desarrollo (npm run dev), la función getStaticProps ¡se va a ejecutar cada vez que refresquemos la página! Esto tiene mucho sentido, porque no queremos ver los mismos datos cada vez que refresquemos la página en modo desarrollo. Para entender mejor cómo se diferencia SSG de SSR, corramos nuestra aplicación en modo producción.

# Paremos el servidor que está corriendo en el puerto 3000.
# Para parar el servidor, presiona ctrl + c
# Cuando el servidor esté detenido, corre:
npm run build
npm run start

Ahora, visitemos http://localhost:3000/ssg. A diferencia de SSR, no importa cuantas veces refresquemos la página, ¡siempre vamos a tener la misma información!

¿Cuándo debo usar SSR y cuándo debo usar SSG?

Esta es una de las preguntas más frecuentes al momento de usar Next.js (si no es que es la más frecuente). Desde mi experiencia (y siguiendo las recomendaciones del equipo de Next.js) te aconsejo siempre empezar nuestras páginas usando SSG (Static Site Generation) y solo debemos considerar el uso de SSR (Server Side Rendering) en caso que SSG no sea suficiente para lo que queramos hacer. Por ejemplo, si estamos trabajando en un dashboard, recomiendo usar SSG para renderizar parte de la interfaz, y luego cargar los datos desde el cliente.

La única situación en la que recomiendo usar SSR, es cuando necesites tener datos frescos en cada request, y que esos datos sean importantes para el SEO o Crawlers. Un buen caso para usar SSR es cuando queremos crear un sitemap. En ese caso necesitamos que los buscadores, por ejemplo Google, tengan los datos actualizados cada vez que visiten la ruta /sitemap.

¿Quieres mejorar tus habilidades de frontend?