Next.js - Tipos de Generación de Contenido

2023, Febrero 10
Next.js - Tipos de Generación de Contenido

Uno de los conceptos más confusos de entender en Next.js es diferenciar entre las distintas maneras de generar el contenido de tu página o aplicación web.

A continuación te listo las diferentes formas en las que Next puede generar el contenido:

  1. SSR: Server Side Rendering.
  2. CSR: Client Side Rendering.
  3. SSG: Static Site Generation.
  4. ISR: Incremental Static Regeneration.

Server Side Rendering (SSR)

Cuando hablamos de SSR, quiere decir que el servidor genera el contenido de la página y lo envía al cliente. Esto es muy útil para el SEO, ya que los motores de búsqueda pueden indexar el contenido de la página. Sin embargo, esto tiene un coste, ya que el servidor tiene que generar el todo el html de la página cada vez que se solicita. Por esta razón el equipo de Nextjs recomienda utilizar esta funcionalidad solo cuando es estrictamente necesario.

Para poder utilizar esta funcionalidad de Nextjs tenemos que ayudarnos de la siguiente funcion que obligatoriamente tiene que estar en el direcotrio pages:

    
// Se ejecuta en el servidor cada vez que se solicita la página
export async function getServerSideProps(context) {

	// Todo el código que escribamos aquí se ejecutará en el servidor.
	// Podemos hacer peticiones a una API, a una base de datos, etc.

  return {
    props: {}, // Se pasan como props al componente de la página
  }
}
  

Static Site Generation (SSG)

Imagina que tienes un aplicación web la cuál quieres hacer una petición a alguna API para mostar los datos que esta devuelve, y además no quieres que se haga una petición cada vez que se solicita la página porque sabes que el contenido no va a cambiar o es muy difícil que cambie.

Bueno en este caso el equipo de Next pensó en una grandiosa solución llamada SSG. Esta funcionalidad nos permite generar el contenido de la página en el servidor y almacenarlo en un archivo estático. De esta forma, cuando se solicite la página, el servidor no tendrá que generar el contenido, sino que lo servirá directamente desde el archivo estático. Para poder utilizar esta grandiosa característica debes de utilizar la siguiente función:

    
// Se ejecuta en proceso de build de la aplicación y genera un archivo estático
export async function getStaticProps(context) {

	// Todo el código que escribamos aquí se ejecutará en el servidor.
	// Podemos hacer peticiones a una API, a una base de datos, etc.

  return {
	props: {}, // Se pasan como props al componente de la página
  }
}
  

Te estarás preguntando ¿Y que ventajas me sa con respecto al SSR? Bueno, la principal es que el servidor no tiene que generar el contenido de la página cada vez que se solicita, sino que al momento de construir la aplicación se genera el contenido y se almacena en un archivo estático. Esto hace que el servidor no se sobrecargue y que la aplicación sea mucho más rápida, además que no perdemos el SEO.

Client Side Rendering (CSR)

El CSR es la forma más sencilla de generar contenido en una aplicación web. En este caso, el servidor solo envía el html básico de la página y el resto del contenido se genera en el cliente. Esto es muy útil cuando el contenido de la página cambia constantemente, ya que no es necesario generar el contenido en el servidor cada vez que se solicita la página. Si has hecho aplicaciónes web con React seguro que ya has utilizado esta forma de generar contenido.

    
import { useState, useEffect } from 'react'

function Profile() {
  const [data, setData] = useState(null)
  const [isLoading, setLoading] = useState(false)

  useEffect(() => {
    setLoading(true)
    fetch('/api/profile-data')
      .then((res) => res.json())
      .then((data) => {
        setData(data)
        setLoading(false)
      })
  }, [])

  if (isLoading) return <p>Loading...</p> // Esperamos a que se carguen los datos
  if (!data) return <p>No profile data</p> // Si no hay datos, mostramos un mensaje de error

  return (
    <div>
      <h1>{data.name}</h1>
      <p>{data.bio}</p>
    </div>
  )
}
  

La principal desventaja de este tipo de renderizado es que no es muy bueno para el SEO, ya que los motores de búsqueda no pueden indexar el contenido de la página.

Rutas Dinámicas

Imaginemos que estamos desarrollando una aplicación que nos muestre en cada página los datos de un pokemón, para ello nos ayudamos de la POKEAPI. Utilizar el SSR o el SSG nos podría ayudar, pero son miles de pokemons que existen y sería muy tedioso crear una página para cada uno de ellos. Bueno, en este caso podemos utilizar las rutas dinámicas de Nextjs. Para ello debemos de crear un archivo con el nombre de la ruta dinámica y dentro de este archivo debemos de crear una función que se llame getStaticPaths. Esta función nos permite generar las rutas dinámicas que queremos que Nextjs genere. Por ejemplo, si queremos que Nextjs genere las rutas dinámicas de los pokemons del 1 al 151, podemos hacer lo siguiente:

    
// pages/pokemon/[slug].js
// Genera `/pokemon/bulbasaur`, `/pokemon/ivysaur`, ... /pokemon/mew`
export async function getStaticPaths() {
	const pokemons = await fetch('https://pokeapi.co/api/v2/pokemon?limit=151').then((res) => res.json())
	const paths = pokemons.results.map((pokemon) => ({
		params: { slug: pokemon.name },
	}));
  return {
    paths,
    fallback: false,
  }
}

// `getStaticPaths` requiere el uso de `getStaticProps`
export async function getStaticProps({ params }) {
	const { slug } = params;
	const pokemon = await fetch(`https://pokeapi.co/api/v2/pokemon/${slug}`).then((res) => res.json())

  return {
    props: { pokemon },
  }
}

export default function PokemonInfo({ pokemon }) {
  return (
		<div>
			<h1>{pokemon.name}</h1>
			<p>{pokemon.weight}</p>
		</div>
	)
}
  

También sabemos que los pokemons pueden aumentar con cada nueva generación, actualmente son muchos más que 151, entonces ahora si queremos renderizar los nuevos, pero solo hemos trabajado con los primeros 151, podemos hacer lo siguiente:

    
// pages/pokemon/[slug].js
// Ingresamos a /pokemon/chikorita que es el pokemon 152
export async function getStaticPaths() {
	const pokemons = await fetch('https://pokeapi.co/api/v2/pokemon?limit=151').then((res) => res.json())
	const paths = pokemons.results.map((pokemon) => ({
		params: { slug: pokemon.name },
	}));
	return {
		paths,
		fallback: 'blocking', // Indica que si no se encuentra la ruta o el pokemon, se debe de generar
	}
}

export async function getStaticProps({ params }) {
	const { slug } = params;
	try {
		const pokemon = await fetch(`https://pokeapi.co/api/v2/pokemon/${slug}`).then((res) => res.json()); // https://pokeapi.co/api/v2/pokemon/chikorita

		// Si el pokemon existe, lo retornamos en las props del componente
		return {
			props: { pokemon },
			revalidate: 60 * 60 * 24, // Regenera la página cada 24 horas
		}
	} catch (error) {
		// Si el pokemon no existe, redireccionamos a la página de error 404
		return { redirect: { destination: '/404', permanent: false } };
	}
}
export default function PokemonInfo({ pokemon }) {
	return (
		<div>
			<h1>{pokemon.name}</h1>
			<p>{pokemon.weight}</p>
		</div>
	)
}
  

En el ejemplo anterior podemos notar que ahora la función getStaticPaths tiene un nuevo parámetro llamado fallback, este parámetro nos permite indicar que si no se encuentra la ruta o el pokemon, se debe de generar. Si el valor de este parámetro es false, significa que si no se encuentra la ruta o el pokemon, se mostrará un error 404. Si el valor de este parámetro es true o blocking, significa que si no se encuentra la ruta o el pokemon, se debe de generar.

Además, también podemos notar que la función getStaticProps la hemos modificado para que si el pokemon no existe, redireccionemos a la página de error 404. Esto es posible gracias a que la función getStaticProps nos permite retornar un objeto con la propiedad redirect.

Por último, la función getStaticProps tiene un nuevo parámetro llamado revalidate, este parámetro nos permite indicar que cada cuanto tiempo se debe de regenerar la página. Por ejemplo, si el valor de este parámetro es 60 * 60 * 24, significa que la función getStaticProps se ejecutará cada 24 horas, aparte de la primera vez que se ejecutó en el momento de la compilación. Esto es útil cuando tenemos una página que se actualiza constantemente o cada cierto tiempo, por ejemplo, una página de noticias, donde cada cierto tiempo se agregan nuevas noticias.

A esta funcionalidad se la conoce como Incremental Static Regeneration (ISR).