Next.js es un framework de JavaScript que nos permite crear fácilmente sitios web de React listos para salir a producción.

En este artículo profundizaremos en la documentación oficial, desglosando los conceptos básicos, funciones y particularidades de Next.js. Cubriremos los siguientes temas:

¿Por qué usar Next.js?

Next.js ofrece la mejor de las experiencias para el desarrollador, con múltiples funcionalidades incorporadas que garantizan la creación de proyectos sólidos listos para producción, estas son algunas de las razones para su uso:

  1. Facilidad para aprender y sin complejas configuraciones.
  2. Optimizado para brindar mejoras de SEO y rendimiento.
  3. Enrutamiento basado en páginas con soporte de rutas dinámicas.
  4. Rutas API desplegadas en funciones Serverless.
  5. Capacidad para generar sitios estáticos (SSG), usar server-side rendering (SSR) o una combinación de ambos según la necesidad de cada página.
  6. Compatibilidad incorporada con CSS y SASS. Además de soporte para múltiples librerías de CSS en JS.

Creando nuestra primera aplicación con Next.js

Requisitos Previos

Antes que nada debes asegurarte de que tienes instalado Node.js en tu ordenador (v10.13 en adelante). Para ello, ejecutamos el siguiente comando desde el terminal:

node -v

Este comando te devolverá la versión de Node que tienes instalada actualmente.

Usando create-next-app

La manera fácil y recomendada de crear nuevas aplicaciones de Next.JS es usando create-next-app, este se encargará de hacer todas las configuraciones básicas necesarias para empezar nuestro proyecto de inmediato. Ejecuta en tu terminal:

npx create-next-app <nombre-de-aplicacion>

Aquí <nombre-de-aplicacion> es el nombre que tendrá la carpeta que alojará los archivos de nuestro proyecto, por lo que puedes darle el nombre de tu preferencia.

Luego de que se complete la instalación, podemos iniciar el servidor de desarrollo ejecutando los siguientes comandos:

cd <nombre-de-aplicacion>
npm run dev

Si visitas localhost:3000 podrás ver en vivo tu nueva aplicación de Next.js.

image-11

Manualmente

También puedes utilizar Next.js instalando manualmente los paquetes: next, react y react-dom:

npm install next react react-dom

Luego abrimos el archivo package.json y añadimos los siguientes scripts:

"scripts": {
  "dev": "next dev",
  "build": "next build",
  "start": "next start"
}
  • dev inicia Next.js en modo desarrollo.
  • start inicia Next.js en modo producción.
  • build construye tu aplicación de Next.js para producción.

Páginas y Rutas

Una de las particularidades de Next.js es que esta construido alrededor del concepto de páginas.

Una página es un componente de React exportado desde la carpeta pages.

Las páginas están asociadas con una ruta basada en el nombre del archivo. Por ejemplo pages/perfil.js resultará en la ruta /perfil.

export default function Perfil() {
  return <div>¡Bienvenido a mi perfil!</div>;
}
pages/perfil.js

Prueba el código anterior por tu cuenta y visita localhost:3000/perfil para ver los resultados.

Rutas Index

Los archivos con nombre index dirigen hacia la raíz del directorio que lo contiene.

  • pages/index.js/
  • pages/blog/index.js/blog

Rutas Anidadas

Supongamos que queremos acceder a la siguiente ruta: /blog/post/:id

Necesitaremos anidar las carpetas de la siguiente manera:

|- pages
  |- index.js
  |- blog
    |- post
      |- [id].js # id dinámico para cada post

Páginas con Rutas Dinámicas

También podemos utilizar rutas dinámicas si agregamos corchetes al nombre del archivo. Por ejemplo, si creamos un archivo llamado pages/post/[id].js podremos acceder a el en las rutas post/1, post/2, y así sucesivamente.

import { useRouter } from "next/router";

export default function Post() {
  const router = useRouter();
  const { id } = router.query;

  return <p>Post: {id}</p>;
}
pages/post/[id].js

Como puedes observar, en el código anterior utilizamos el hook useRouter de Next.js para acceder al objeto router, dicho objeto contiene propiedades muy útiles, las partes dinámicas de cada ruta se almacenan en router.query.

Enlazando páginas

Para obtener una navegación fluida entre las páginas de nuestra aplicación, necesitamos importar el componente Link y usarlo de la siguiente manera:

import Link from "next/link";

export default function Home() {
  return (
    <ul>
      <li>
        <Link href="/">
          <a>Home</a>
        </Link>
      </li>
      <li>
        <Link href="/perfil">
          <a>Mi perfil</a>
        </Link>
      </li>
      <li>
        <Link href="/blog/post/hola-mundo">
          <a>Post</a>
        </Link>
      </li>
    </ul>
  );
}
pages/index.js

En el ejemplo anterior podemos observar que hay múltiples enlaces, cada uno cuenta con un componente Link que recibe una propiedad href indicando la ruta a la que dirige. Link envuelve un elemento Anchor a que contiene el texto de nuestro enlace.

Enlazando Rutas Dinámicas

También puedes utilizar interpolación para crear la ruta, lo que resulta útil para los segmentos de ruta dinámicos. Por ejemplo, para mostrar una lista de posts que se han pasado al componente como propiedad:

import Link from "next/link";

export default function Posts({ posts }) {
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>
          <Link href={`/blog/post/${post.id}`}>
            <a>{post.titulo}</a>
          </Link>
        </li>
      ))}
    </ul>
  );
}

O usando un objeto URL:

import Link from "next/link";

export default function Posts({ posts }) {
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>
          <Link
            href={{
              pathname: "/blog/post/[id]",
              query: { id: post.id },
            }}
          >
            <a>{post.titulo}</a>
          </Link>
        </li>
      ))}
    </ul>
  );
}

CSS en Next.js

Existen muchas formas de darle estilo a tu aplicación en Next.js, puedes importar archivos de hojas de estilo directamente gracias a la compatibilidad con los Módulos de CSS. Para ello el archivo debe nombrarse de la siguiente manera: [nombre].module.css.

Los Módulos de CSS mantienen un ámbito local creando clases únicas automáticamente, por lo que te permite usar los mismos nombres de clases en diferentes archivos sin que tengas que preocuparte por colisiones.

Por ejemplo, para crear un componente botón reusable, primero creamos componentes/Boton.module.css con el siguiente contenido:

.peligro {
  color: white;
  background-color: red;
}
componentes/Boton.module.css

Y un archivo componentes/Boton.js donde importar y usar el módulo CSS antes creado.

import estilos from "./Boton.module.css";

export default function Boton() {
  return (
    <button type="button" className={estilos.peligro}>
      Borrar
    </button>
  );
}
componentes/Boton.js
Nota: La clase peligro es una propiedad del objeto estilos importado.

Así de fácil es usar los Módulos de CSS en Next.js, recuerda que también tenemos más opciones de estilo a nuestra disposición, tales como Sass, Less o CSS en JavaScript.

Recursos estáticos

La carpeta public es utilizada en Next.js para servir todos nuestros recursos estáticos (imágenes, iconos, robots, entre otros). Puedes importar archivos dentro de la carpeta public  usando (/) como URL base.

Por ejemplo, para acceder a una imagen guardada en public/hero.jpg escribimos un código como el siguiente:

export default function Home() {
  return (
    <div>
      <img src="/hero.jpg" />
    </div>
  );
}
pages/index.js
Nota: No cambies el nombre de la carpeta public por ningún otro, es la única que puede servir recursos estáticos.

SSG vs SSR

Pre-renderizado

Next.js pre-renderiza cada página por defecto. Generando el HTML por adelantado en lugar de dejarle todo el trabajo al JavaScript del cliente. Esto se traduce en importantes mejoras de rendimiento y SEO.

Cada HTML generado se asocia con un mínimo de código de JavaScript requerido para esa página. Cuando una página es cargada por el navegador, su código JavaScript se ejecuta y hace la página plenamente interactiva. Este proceso es llamado hidratación.

Tipos de Pre-renderizado

Existen dos formas de pre-renderizado en Next.js: Static Site Generation y Server-side Rendering. La diferencia principal radica en cuando se genera el HTML para la página.

  • SSG (Static Site Generation): El HTML se genera antes de cada petición, como al momento de crear la build.
  • SSR (Server-side Rendering): El HTML se genera en el servidor durante cada petición.

Static Site Generation

Si nuestra página no requiere datos de una fuente externa, Next.js se encargará de generarla de forma estática al crear la build. Por ejemplo:

export default function Perfil() {
  return <div>Perfil</div>;
}

En cambio, si el contenido o rutas de la página dependen de una fuente externa, necesitaremos usar dos funciones especiales de Next.js: getStaticProps y getStaticPaths.

Ejemplo 1: Digamos que tu página necesita una lista de posts publicados en tu blog desde un CMS.

// TODO: Hacer fetch a los `posts` antes de que se pre-renderice la página
export default function Blog({ posts }) {
  return (
    <ul>
      {posts.map((post) => (
        <li key={post.id}>{post.titulo}</li>
      ))}
    </ul>
  );
}

En el mismo archivo tenemos que exportar la función asíncrona getStaticProps, esta función es llamada al momento de crear la build y te permite pasar los datos recopilados a la página en forma de props.

export async function getStaticProps() {
  // Hacer fetch al endpoint que contiene los `posts`
  const res = await fetch("https://.../posts");
  const posts = await res.json();

  return { props: { posts } };
}

export default function Blog({ posts }) {
  // Renderizar posts...
}

Ejemplo 2: Queremos pre-renderizar posts individuales usando rutas dinámicas.

Empezamos creando un archivo con ruta dinámica llamado pages/posts/[id].js, este nos permitirá acceder a nuestros posts según su id, con enlaces como: /posts/1, /posts/2, etc.

export default function Post({ post }) {
  return (
    <div>
      <h1>{post.titulo}</h1>
      <p>{post.contenido}</p>
    </div>
  );
}

Podemos indicar a Next.js las rutas dinámicas que queremos pre-renderizar usando la función getStaticPaths.

export async function getStaticPaths() {
  // Hacer fetch al endpoint que contiene los `posts`
  const res = await fetch("https://.../posts");
  const posts = await res.json();

  // Obtener rutas a pre-renderizar basado en el `id` de los posts
  const rutas = posts.map((post) => `/posts/${post.id}`);

  return { paths: rutas, fallback: false };
}
Nota: { fallback: false } hace que las rutas no incluidas devuelvan una página 404.

Finalmente, utilizamos getStaticProps para conseguir los datos del post según el id proporcionado en el objeto params.

export async function getStaticPaths() {
  // ...
}

export async function getStaticProps({ params }) {
  // params contiene el `id` del post.
  const res = await fetch(`https://.../posts/${params.id}`);
  const post = await res.json();

  // Pasar datos del post hacia la página en forma de props
  return { props: { post } };
}

export default function Post({ post }) {
  // Renderizar post...
}

Server-side Rendering

Muchas veces los datos de nuestra página deben ser constantemente actualizados o tal vez necesitemos interactuar con una API haciendo consultas variadas y complejas. Para casos como estos, Next.js nos permite usar Server-side Rendering (SSR) en nuestras aplicaciones de React.

Cuando una página utiliza Server-side rendering, el HTML de la misma se genera en el servidor durante cada petición. Para ello, necesitamos exportar la función asíncrona getServerSideProps, la cual recopila los datos y los pasa a la página.

// Esta función es llamada durante cada petición
export async function getServerSideProps() {
  // Recopilar datos desde una API externa
  const res = await fetch(`https://.../datos`);
  const datos = await res.json();

  // Pasar datos hacia la pagina en forma de props
  return { props: { datos } };
}

export default function Pagina({ datos }) {
  // Renderizar datos...
}

Como puedes observar, getServerSideProps y getStaticProp cuentan con una sintaxis y funcionamiento similar, pero se diferencian principalmente por el momento en que son ejecutadas.

Rutas API

Next.js provee una solución para construir APIs fácilmente en un entorno Serverless (Sin servidor), por lo que no tendremos que preocuparnos de pagar alojamiento de servidores para el back-end de nuestra aplicación.

Todos los archivos dentro de la carpeta pages/api serán tratados como endpoints de nuestra API en lugar de páginas.

Por ejemplo, si creamos un archivo pages/api/hola.js podremos acceder al mismo en la ruta: /api/hola y nos devolverá como respuesta un objeto json.

export default function manejador(req, res) {
  res.status(200).json({ texto: "Hola" });
}
pages/api/hola.js

Como puedes observar, para que este archivo funcione correctamente como API endpoint es necesario exportar una función manejador (handler) con los parámetros req y res.

Si queremos tener acceso a los diferentes métodos HTTP en nuestro manejador podemos usar req.method dentro del mismo, por ejemplo:

export default function manejador(req, res) {
  switch (req.method) {
    case "GET":
      // Nuestra lógica de código para el método GET...
      break;
    case "PATCH":
      // Nuestra lógica de código para el método PATCH...
      break;
    case "DELETE":
      // Nuestra lógica de código para el método DELETE...
      break;
    default:
      res.status(405).json({
        mensaje: `El método HTTP ${req.method} no esta disponible en esta ruta`,
      });
      break;
  }
}

Si ya estás familiarizado con Express.js probablemente encuentres muchas similitudes en cuanto a sintaxis.

Palabras finales

En este artículo pudimos explorar algunas de las funcionalidades principales de Next.js, mostrando una visión general de sus características, herramientas y ventajas tanto para el desarrollador como para el usuario final.

Espero que esta lectura haya sido de tu agrado y te animes a probar este framework por tu cuenta. Pienso más adelante crear contenido de Next.js tocando temas más específicos, tales como: middlewares, opciones de despliegues, enrutamiento internacionalizado (i18n), entres otros.


¿Tienes alguna duda o quieres mantenerte al día con mis publicaciones? Sígueme en Twitter.