Original article: The Next.js Handbook – Learn Next.js for Beginners

Escribí este tutorial para ayudarte a aprender rápidamente Next.js y familiarizarte con su funcionamiento.

Es ideal para ti si tienes cero o poco conocimiento de Next.js, has usado React en el pasado y estás deseando sumergirte más en el ecosistema React, en particular en el renderizado del lado del servidor (server-side rendering).

Next.js me parece una herramienta impresionante para crear Aplicaciones Web, al final de este post espero que estés tan entusiasmado como yo. ¡Espero que te ayude a aprender Next.js!

Nota: puede descargar una versión PDF / ePub / Mobi de este tutorial para poder leerlo sin conexión.

Índice

  1. Introducción
  2. Principales características de Next.js
  3. Next.js vs Gatsby vs create-react-app
  4. Cómo instalar Next.js
  5. Revisar código fuente para confirmar que SSR funciona
  6. Los paquetes de aplicaciones
  7. ¿Qué es ese icono en la parte inferior derecha?
  8. Instalar React DevTools
  9. Otras técnicas de depuración que puedes utilizar
  10. Añadir una segunda página al sitio web
  11. Enlazar las dos páginas
  12. Contenido dinámico con el router
  13. Prefetching(Búsqueda previa)
  14. Uso del router para detectar el enlace activo
  15. Uso de next/router
  16. Alimentación de datos a los componentes mediante getInitialProps()
  17. CSS
  18. Rellenar la etiqueta head con etiquetas personalizadas
  19. Añadir un componente envolvente (Wrapper component)
  20. Rutas API
  21. Ejecutar código en el lado del servidor o en el lado del cliente
  22. Despliegue de la versión de producción
  23. Despliegue en Now
  24. Análisis de los paquetes de aplicaciones
  25. Módulos de carga lenta
  26. Qué hacer a partir de ahora

Introducción

Trabajar en una aplicación JavaScript moderna impulsada por React es increíble hasta que te das cuenta de que hay un par de problemas relacionados con la representación de todo el contenido en el lado del cliente.

En primer lugar, la página tarda más en ser visible para el usuario, porque antes de que se cargue el contenido, debe cargarse todo el JavaScript, y su aplicación necesita ejecutarse para determinar qué mostrar en la página.

En segundo lugar, si usted está construyendo un sitio web disponible al público, tiene un problema de SEO de contenido. Los motores de búsqueda están mejorando en la ejecución e indexación de aplicaciones JavaScript, pero es mucho mejor si podemos enviarles el contenido en lugar de dejar que ellos lo descubran.

La solución a ambos problemas es el renderizado del lado del servidor (server rendering), también llamado "pre-renderizado estático" (static pre-rendering).

Next.js es un framework inspirado en React para hacer todo esto de una manera muy sencilla, pero no se limita a esto. Sus creadores lo anuncian como una cadena de herramientas de configuración cero y una interfaz de herramientas para aplicaciones en React.

Proporciona una estructura común que te permite construir fácilmente un sitio web con React y maneja de forma transparente el renderizado del lado servidor (server-side rendering).

Principales características de Next.js

He aquí una lista no exhaustiva de las principales características de Next.js:

Recarga en caliente (Hot Code Reloading)

Next.js recarga la página cuando detecta algún cambio guardado en disco.

Enrutamiento automático (Automatic Routing)

Cualquier URL es mapeada al sistema de archivos, los cuales se ubican en la carpeta pages. No necesitas ninguna configuración (por supuesto, tienes opciones de personalización).

Componentes de un solo archivo (Single File Components)

Usando styled-jsx, completamente integrado y construido por el mismo equipo, es trivial añadir estilos al componente.

Renderización del lado del servidor (Server Rendering)

Puedes renderizar componentes React en el lado del servidor, antes de enviar el HTML al cliente.

Compatibilidad con el ecosistema

Next.js funciona bien con el resto del ecosistema JavaScript, Node y React.

Separación automática del código

Las páginas se renderizan sólo con las librerías y el JavaScript que requieren, nada más. En lugar de generar un único archivo JavaScript con todo el código de la aplicación, Next.js la divide automáticamente en varios recursos diferentes.

Al cargar una página sólo se carga el JavaScript necesario para esa página en concreto.

Next.js lo hace analizando los recursos importados.

Si sólo una de sus páginas importa la biblioteca Axios, por ejemplo, esa página específica incluirá la biblioteca en su paquete.

Esto garantiza que la carga de la primera página sea lo más rápida posible y que sólo las cargas de páginas futuras (si es que se activan) envíen el JavaScript necesario al cliente.

Hay una excepción notable. Las importaciones de uso frecuente se trasladan al paquete principal de JavaScript si se utilizan en al menos la mitad de las páginas del sitio.

Precarga (Prefetching)

El componente Link, utilizado para enlazar distintas páginas, admite una función de prefetch que precarga automáticamente los recursos de la página (incluido el código que falta debido a la separación del código) en segundo plano.

Componentes dinámicos (Dynamic Components)

Puedes importar módulos JavaScript y componentes React de forma dinámica.

Exportación estática (Static Exports)

Usando el comando next export, Next.js te permite exportar un sitio completamente estático de tu aplicación.

Compatibilidad con TypeScript

Next.js está escrito en TypeScript y como tal viene con un excelente soporte para TypeScript.

Next.js vs Gatsby vs create-react-app

Next.js, Gatsby y create-react-app son herramientas increíbles que podemos utilizar para potenciar nuestras aplicaciones.

Digamos primero lo que tienen en común. Todas tienen React bajo el capó, potenciando toda la experiencia de desarrollo. También abstraen webpack y todas esas cosas de bajo nivel que solíamos configurar manualmente en los viejos tiempos.

create-react-app no te ayuda a generar una aplicación renderizada del lado del servidor fácilmente. Todo lo que viene con ello (SEO, velocidad...) sólo lo proporcionan herramientas como Next.js y Gatsby.

¿Cuándo es Next.js mejor que Gatsby?

Ambos pueden ayudar con el renderizado del lado del servidor  (server-side rendering), pero de 2 maneras diferentes.

El resultado final usando Gatsby es un generador de sitios estáticos, sin servidor. Usted construye el sitio, y luego despliega el resultado del proceso de construcción estáticamente en Netlify u otro sitio de alojamiento estático.

Next.js proporciona un backend que puede renderizar del lado del servidor una respuesta a la petición, lo que permite crear un sitio web dinámico, lo que significa que se desplegará en una plataforma que pueda ejecutar Node.js.

Next.js también puede generar un sitio estático, pero yo no diría que es su principal caso de uso.

Si mi objetivo fuera construir un sitio estático, me costaría elegir y quizás Gatsby tenga un mejor ecosistema de plugins, incluyendo muchos para blogging en particular.

Gatsby también se basa en gran medida en GraphQL, algo que puede gustarte o disgustarte dependiendo de tus opiniones y necesidades.

¿Cómo instalar Next.js?

Para instalar Next.js, necesitas tener instalado Node.js.

Asegúrate de que tienes la última versión de Node. Compruébalo ejecutando node -v en tu terminal y compárala con la última versión LTS listada en https://nodejs.org/.

Después de instalar Node.js, tendrás el comando npm disponible en tu línea de comandos.

Si tienes algún problema en esta 1 fase, te recomiendo los siguientes tutoriales que he escrito para ti:

Ahora que tienes Node actualizado a la última versión y npm, ¡estamos listos!

Ahora podemos elegir 2 rutas: usar create-next-app o el enfoque clásico que implica instalar y configurar una aplicación Next manualmente.

Usando create-next-app

Si estás familiarizado con create-react-app,  create-next-app es lo mismo - excepto que crea una aplicación Next en lugar de una aplicación React, como su nombre indica.

Supongo que ya tienes instalado Node.js, desde la versión 5.2 (hace más de 2 años en el momento de escribir esto), viene con el comando npx incluido. Esta herramienta útil nos permite descargar y ejecutar un comando JavaScript y lo usaremos así:

npx create-next-app

El comando pregunta el nombre de la aplicación (y crea una nueva carpeta para ti con ese nombre), luego descarga todos los paquetes que necesita ( react, react-dom, next), establece el package.json a:

package.json
package.json

y puedes ejecutar inmediatamente la aplicación de ejemplo ejecutando npm run dev:

Inicio de ejecución Next js
Inicio de ejecución Next js

Y aquí está el resultado en http://localhost:3000:

Aplicación iniciada en el navegador
Aplicación iniciada en el navegador

Esta es la forma recomendada de iniciar una aplicación Next.js, ya que te proporciona estructura y código de ejemplo con el que jugar. Hay más ejemplos aparte del predeterminado; puedes usar cualquiera de los ejemplos almacenados en https://github.com/zeit/next.js/tree/canary/examples usando la opción --example. Por ejemplo, prueba:

npx create-next-app --example blog-starter

Lo que le da una instancia de blog inmediatamente utilizable con resaltado de sintaxis también:

Aplicación de ejemplo blog
Aplicación de ejemplo blog

Crear manualmente una aplicación Next.js

Puedes evitar create-next-app si te apetece crear una aplicación Next desde cero. He aquí cómo: crea una carpeta vacía donde quieras, por ejemplo en tu carpeta de inicio y entra en ella:

mkdir nextjs
cd nextjs

y crea tu primer directorio del proyecto Next:

mkdir firstproject
cd firstproject

Ahora usa el comando npm para inicializarlo como un proyecto Node:

npm init -y

La opción -y indica a npm que utilice la configuración predeterminada para un proyecto, rellenando un archivo package.json de ejemplo.

Inicializando proyecto con npm init -y
Inicializando proyecto con npm init -y

Ahora instala Next y React:

npm install next react react-dom

Su carpeta de proyecto debe tener ahora 2 archivos:

y la carpeta node_modules.

Abra la carpeta del proyecto utilizando su editor favorito. Mi editor favorito es VS Code. Si lo tienes instalado, puedes ejecutar code . en tu terminal para abrir la carpeta actual en el editor (si el comando no te funciona, mira esto)

Abra package.json, que ahora tiene este contenido:

{
  "name": "firstproject",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies":  {
    "next": "^9.1.2",
    "react": "^16.11.0",
    "react-dom": "^16.11.0"
  }
}

y reemplazar la sección de scripts con:

"scripts": {
  "dev": "next",
  "build": "next build",
  "start": "next start"
}

Para añadir los comandos de compilación de Next.js, que usaremos pronto.

Consejo: usa "dev": "next -p 3001", para cambiar el puerto y ejecutar, en este ejemplo, en el puerto 3001.

Screen-Shot-2019-11-04-at-17.01.03

Ahora crea una carpeta pages y añade un archivo index.js.

En este archivo, vamos a crear nuestro primer componente React.

Vamos a utilizarlo como la exportación por defecto:

const Index = () => (
  <div>
    <h1>Home page</h1>
  </div>
)

export default Index

Ahora, usando el terminal, ejecuta npm run dev para iniciar el servidor de desarrollo Next.

Esto hará que la aplicación esté disponible en el puerto 3000, en localhost.

Ejecución del proyecto con npm run dev
Ejecución del proyecto con npm run dev

Abra http://localhost:3000 en su navegador para verlo.

Componente con el texto Home page
Componente con el texto Home page

Ver código fuente para confirmar que (Renderizado del lado del servidor) SSR está trabajando

Comprobemos ahora que la aplicación funciona como esperamos. Es una aplicación Next.js, por lo que debería renderizarse del lado del servidor (server side rendered).

Es uno de los principales argumentos de venta de Next.js: si creamos un sitio utilizando Next.js, las páginas del sitio se renderizan en el servidor, que entrega HTML al navegador.

Esto tiene 3 ventajas principales:

  • El cliente no necesita instanciar React para renderizar, lo que hace que el sitio sea más rápido para sus usuarios.
  • Los motores de búsqueda indexarán las páginas sin necesidad de ejecutar el JavaScript del lado del cliente. Algo que Google comenzó a hacer, pero admitió abiertamente que es un proceso más lento (y debes ayudar a Google tanto como sea posible, si quieres clasificar bien).
  • Puede tener metaetiquetas de medios sociales, útiles para añadir imágenes de vista previa, personalizar el título y la descripción de cualquiera de sus páginas compartidas en Facebook, Twitter, etc.

Vamos a ver la fuente de la aplicación.

Usando Chrome puede hacer clic derecho en cualquier lugar de la página, y pulse Ver fuente de la página(View Page Source).

Código fuente construido
Código fuente construido

Si ves el código fuente de la página, verás el fragmento <div><h1>Home page</h1></div> en el body del HTML, junto con un montón de archivos JavaScript: los paquetes de la aplicación.

No necesitamos configurar nada, SSR (Renderizado del lado del servidor) ya está trabajando para nosotros.


La aplicación React se ejecutará en el cliente y será el que ejecute las interacciones como hacer clic en un enlace, utilizando la renderización del lado del cliente. Pero al recargar una página se volverá a cargar desde el servidor. Y usando Next.js no debería haber ninguna diferencia en el resultado dentro del navegador - una página renderizada desde el servidor debería verse exactamente igual que una página renderizada desde el cliente.

Paquete de aplicación (app bundle)

Cuando revisamos el código fuente de la página, vimos que se estaban cargando un montón de archivos JavaScript:

Scripts cargando en el código fuente
Scripts cargando en el código fuente

Empecemos por poner el código en un formateador HTML para formatearlo mejor de modo que los humanos tengamos más posibilidades de entenderlo:

<!DOCTYPE html>
<html>

<head>
    <meta charSet="utf-8" />
    <meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1" />
    <meta name="next-head-count" content="2" />
    <link rel="preload" href="/_next/static/development/pages/index.js?ts=1572863116051" as="script" />
    <link rel="preload" href="/_next/static/development/pages/_app.js?ts=1572863116051" as="script" />
    <link rel="preload" href="/_next/static/runtime/webpack.js?ts=1572863116051" as="script" />
    <link rel="preload" href="/_next/static/runtime/main.js?ts=1572863116051" as="script" />
</head>

<body>
    <div id="__next">
        <div>
            <h1>Home page</h1></div>
    </div>
    <script src="/_next/static/development/dll/dll_01ec57fc9b90d43b98a8.js?ts=1572863116051"></script>
    <script id="__NEXT_DATA__" type="application/json">{"dataManager":"[]","props":{"pageProps":{}},"page":"/","query":{},"buildId":"development","nextExport":true,"autoExport":true}</script>
    <script async="" data-next-page="/" src="/_next/static/development/pages/index.js?ts=1572863116051"></script>
    <script async="" data-next-page="/_app" src="/_next/static/development/pages/_app.js?ts=1572863116051"></script>
    <script src="/_next/static/runtime/webpack.js?ts=1572863116051" async=""></script>
    <script src="/_next/static/runtime/main.js?ts=1572863116051" async=""></script>
</body>

</html>
Página principal construida

Tenemos 4 archivos JavaScript declarados para ser precargados en la etiqueta head, usando rel="preload" as="script":

  • /_next/static/development/pages/index.js (96 LOC)
  • /_next/static/development/pages/_app.js (5900 LOC)
  • /_next/static/runtime/webpack.js (939 LOC)
  • /_next/static/runtime/main.js (12k LOC)

Esto indica al navegador que empiece a cargar esos archivos lo antes posible, antes de que comience el flujo normal de renderizado. Sin ellos, los scripts se cargarían con un retraso adicional, esto mejora el rendimiento de carga de la página.

Entonces esos 4 archivos se cargan al final del body, junto con /_next/static/development/dll/dll_01ec57fc9b90d43b98a8.js (31k LOC), y un fragmento JSON que establece algunos valores por defecto para los datos de la página:

<script id="__NEXT_DATA__" type="application/json">
{
  "dataManager": "[]",
  "props": {
    "pageProps":  {}
  },
  "page": "/",
  "query": {},
  "buildId": "development",
  "nextExport": true,
  "autoExport": true
}
</script>
Script de Next Js

Los 4 archivos bundle ya cargados, están implementando una característica llamada división de código. El archivo index.js proporciona el código necesario para el componente index, que sirve a la ruta /, si tuviéramos más páginas tendríamos más paquetes para cada página, que sólo se cargarán si es necesario - para proporcionar un tiempo de carga más eficiente para la página.

¿Qué es ese icono de abajo a la derecha?

¿Has visto ese pequeño icono en la parte inferior derecha de la página, que parece un rayo?

Screen-Shot-2019-11-04-at-13.21.42

Si pasas el ratón por encima, dirá "Página prerrenderizada":

Screen-Shot-2019-11-04-at-13.21.46

Este icono, que sólo es visible en el modo de desarrollo, por supuesto, le dice que la página califica para la optimización estática automática, lo que básicamente significa que no depende de los datos que deben obtenerse en el momento de la invocación y puede ser prerenderizada y construida como un archivo HTML estático en tiempo de compilación (cuando ejecutamos npm run build).

A continuación podemos determinar esto por la ausencia del método getInitialProps() adjunto al componente de página.

Cuando este es el caso, nuestra página puede ser aún más rápida porque se servirá estáticamente como un archivo HTML en lugar de pasar por el servidor Node.js que genera la salida HTML.

Otro icono útil que puede aparecer junto a él o en lugar de él en páginas no pre-renderizadas, es un pequeño triángulo animado:

Screen-Shot-2019-11-14-at-14.56.21

Este es un indicador de compilación, aparece cuando guardas una página y Next.js está compilando la aplicación antes de que la recarga de código en caliente (hot code reloading) se active para recargar el código en la aplicación automáticamente.

Es una forma muy agradable de determinar inmediatamente si la aplicación ya ha sido compilada y puedas probar la parte en la que estás trabajando.

Instalar React Developer Tools

Next.js se basa en React, por lo que una herramienta muy útil que es absolutamente necesario instalar (si aún no lo ha hecho) es React Developer Tools.

Disponible tanto para Chrome como para Firefox, las React Developer Tools son un instrumento esencial que puedes utilizar para inspeccionar una aplicación React.

Ahora, las React Developer Tools no son específicas de Next.js pero quiero presentarlas porque puede que no estés 100% familiarizado con todas las herramientas que React proporciona. Es mejor adentrarse un poco en las herramientas de depuración que asumir que ya las conoces.

Estas proporcionan un inspector que revela el árbol de componentes de React que construye tu página y para cada componente puedes ir a comprobar los props, el estado, hooks y mucho más.

Una vez que hayas instalado las React Developer Tools, puedes abrir las devtools del navegador normal (en Chrome, es hacer clic con el botón derecho en la página, y luego hacer clic en Inspect) y encontrarás 2 nuevos paneles: Components y Profiler.

React Developer Tools
React Developer Tools

Si mueves el ratón sobre los componentes, verás que en la página, el navegador seleccionará las partes que son renderizadas por ese componente.

Si seleccionas cualquier componente en el árbol, el panel derecho te mostrará una referencia al componente padre y los props pasados a él:

Componente Index en React Developer Tools
Componente Index en React Developer Tools

Puedes navegar fácilmente haciendo clic alrededor de los nombres de los componentes.

Puedes hacer clic en el icono del ojo en la barra de herramientas de desarrollador para inspeccionar el elemento DOM, también si utilizas el primer icono, el del ratón (que convenientemente se encuentra debajo del similar icono DevTools regular), puedes pasar por un elemento en la interfaz de usuario del navegador para seleccionar directamente el componente React que lo renderiza.

Puedes usar el icono del error (bug) para registrar los datos de un componente en la consola.

Componentes en la consola
Componentes en la consola

Esto es bastante impresionante porque una vez que tienes los datos impresos allí, puedes hacer clic derecho en cualquier elemento y pulsar "Almacenar como variable global". Por ejemplo aquí lo hice con la prop url pude inspeccionarla en la consola usando la variable temporal que se le asignó temp1:

Variables globales
Variables globales

Usando Source Maps, que son cargados por Next.js automáticamente en modo desarrollo, desde el panel Componentes podemos pulsar el código <> y el DevTools cambiará al panel Fuente, mostrándonos el código fuente del componente:

Componente Index en tab source del navegador
Componente Index en tab source del navegador

La pestaña Profiler es aún más impresionante, si cabe. Nos permite grabar una interacción en la app para ver qué ocurre. No puedo mostrar un ejemplo todavía, porque se necesitan al menos 2 componentes para crear una interacción, y ahora sólo tenemos uno. Hablaré de esto más adelante.

React Dev Tools - Profiler
React Dev Tools - Profiler

He mostrado todas las capturas de pantalla usando Chrome, pero React Developer Tools funciona de la misma manera en Firefox:

React Dev Tools - Firefox
React Dev Tools - Firefox

Otras técnicas de depuración que puede utilizar

Además de las React Developer Tools, que son esenciales para construir una aplicación Next.js, quiero hacer hincapié en 2 formas de depurar aplicaciones en Next.js.

La primera es obviamente console.log() y todas las demás herramientas de la API de Consola. La forma en que funcionan las aplicaciones Next hará que una declaración de tipo log funcione en la consola del navegador o en la terminal donde iniciaste Next usando npm run dev.

En particular, si la página se carga desde el servidor, cuando apuntas la URL a ella, o pulsas el botón refrescar / cmd/ctrl-R, cualquier registro de consola ocurre en la terminal.

Las subsiguientes transiciones de página que ocurran al pulsar el ratón harán que todo el registro de consola ocurra dentro del navegador.

Sólo recuerde si usted se sorprende por la falta de registro de mensajes.

Otra herramienta que es esencial es la palabra debugger. Añadiendo esta sentencia a un componente se pausará el renderizado de la página por parte del navegador:

debugger en componente Index
debugger en componente Index

Realmente impresionante porque ahora puedes usar el depurador del navegador para inspeccionar valores y ejecutar tu aplicación línea a línea.

También puedes usar el depurador de VS Code para depurar código del lado del servidor. Menciono esta técnica y este tutorial para configurar esto.

Añadir una segunda página al sitio web

Ahora que tenemos un buen conocimiento de las herramientas que podemos utilizar para ayudarnos a desarrollar aplicaciones Next.js, vamos a continuar desde donde dejamos nuestra primera aplicación:

Localhost en Firefox
Localhost en Firefox

Quiero añadir una segunda página a este sitio web, un blog. Va a ser servido en /blog y por el momento sólo contendrá una simple página estática, al igual que nuestro primer componente index.js:

Componente Blog
Componente Blog

Después de guardar el nuevo archivo, el proceso npm run dev ya en ejecución es capaz de renderizar la página, sin necesidad de reiniciarlo.

Cuando pulsamos la URL http://localhost:3000/blog tenemos la nueva página:

Ruta con componente Blog
Ruta con componente Blog

y esto es lo que nos dijo el terminal:

Terminal con estado exitoso
Terminal con estado exitoso

Ahora el hecho de que la URL sea /blog depende sólo del nombre del archivo y de su posición dentro de la carpeta pages.

Puedes crear una página pages/hey/ho y esta página aparecerá en la URL http://localhost:3000/hey/ho.

Lo que no importa, a efectos de la URL, es el nombre del componente dentro del archivo.

Intenta ir y ver el código fuente de la página, cuando se carga desde el servidor listará /_next/static/development/pages/blog.js como uno de los paquetes cargados y no /_next/static/development/pages/index.js como en la página de inicio. Esto se debe a que gracias a la división automática del código no necesitamos el bundle que sirve a la página de inicio. Sólo el paquete de aplicación(bundle) que sirve la página del blog.

Código fuente de ruta /blog
Código fuente de ruta /blog

También podemos simplemente exportar una función anónima desde blog.js:

export default () => (
  <div>
    <h1>Blog</h1>
  </div>
)
Componente Blog con función anónima de flecha

o si prefiere la sintaxis de funciones sin flechas:

export default function() {
  return (
    <div>
      <h1>Blog</h1>
    </div>
  )
}
Componente Blog sin función flecha

Enlazar las dos páginas

Ahora que tenemos 2 páginas, definidas por index.js y blog.js, podemos introducir enlaces.

Los enlaces HTML normales dentro de las páginas se hacen utilizando la etiqueta a:

<a href="/blog">Blog</a>
Etiqueta "a" (Hipervinculos)

No podemos hacer eso en Next.js.

¿Por qué? Técnicamente podemos, claro, porque esto es la Web y en la Web las cosas nunca se rompen (por eso podemos seguir usando la etiqueta <marquee>. Pero una de las principales ventajas de usar Next, es que una vez cargada una página, las transiciones a otra son muy rápidas gracias al renderizado del lado del cliente.

Si usas un simple enlace a:

const Index = () => (
  <div>
    <h1>Home page</h1>
    <a href='/blog'>Blog</a>
  </div>
)

export default Index
Componente Index con etiqueta a

Ahora abre el DevTools, y el panel de Network panel (Panel Red). La primera vez que cargamos http://localhost:3000/ se cargan todos los paquetes de páginas:

Network tab
Network tab

Ahora, si haces clic en el botón "Conservar registro" (para evitar que se borre el panel Red), haces clic en el enlace "Blog", esto es lo que ocurre:

Network tab cargando código
Network tab cargando código

Tenemos todo ese JavaScript del servidor, ¡otra vez! Pero... no necesitamos todo ese JavaScript si ya lo tenemos. Sólo necesitaríamos el paquete de página blog.js, el único que es nuevo en la página.

Para solucionar este problema, utilizamos un componente proporcionado por Next, llamado Link.

Lo importamos:

import Link from 'next/link'
Componente Link en Next Js

y luego lo usamos para envolver nuestro enlace, así:

import Link from 'next/link'

const Index = () => (
  <div>
    <h1>Home page</h1>
    <Link href='/blog'>
      <a>Blog</a>
    </Link>
  </div>
)

export default Index
Componente Link envolviendo etiqueta a

Ahora si vuelves a intentar lo que hicimos anteriormente, podrás ver que sólo se carga el bundle blog.js cuando pasamos a la página del blog:

Screen-Shot-2019-11-04-at-16.35.18

y la página se cargó tan rápido que ni siquiera apareció el spinner habitual del navegador en la pestaña. Sin embargo, la URL cambió, como puedes ver. Esto funciona perfectamente con la API del historial del navegador.

Esto es renderizado del lado del cliente en acción.

¿Y si ahora pulsas el botón Atrás? No se descarga nada, porque el navegador todavía tiene el antiguo paquete index.js, listo para cargar la ruta /index. Todo es automático.

Contenido dinámico con el router

En el capítulo anterior vimos cómo enlazar la página home con la página del blog.

Un blog es un gran caso de uso para Next.js, que seguiremos explorando en este capítulo añadiendo más artículos al blog.

Los artículos de blog tienen una URL dinámica. Por ejemplo, una entrada titulada "Hello World" podría tener la URL /blog/hello-world. Una entrada titulada "My second post" podría tener la URL /blog/my-second-post.

Este contenido es dinámico y puede proceder de una base de datos, archivos markdown u otros.

Next.js puede servir contenido dinámico basado en una URL dinámica.

Creamos una URL dinámica creando una página dinámica con la sintaxis [].

¿Cómo? Añadimos un archivo pages/blog/[id].js. Este archivo manejará todas las URLs dinámicas bajo la ruta /blog/, como las que mencionamos anteriormente: /blog/hello-world, /blog/my-second-post y más.

En el nombre del archivo, [id] dentro de los corchetes significa que cualquier cosa que sea dinámica será puesta dentro del parámetro id de la propiedad query del enrutador.

Vale, son demasiadas cosas a la vez.

¿Qué es el enrutador?

El enrutador es una biblioteca proporcionada por Next.js.

Lo importamos desde next/router:

import { useRouter } from 'next/router'
importar enrutador

y una vez que tenemos useRouter, instanciamos el objeto router usando:

const router = useRouter()
Hook para usar el enrutador

Una vez que tenemos este objeto router, podemos extraer información de él.

En particular, podemos obtener la parte dinámica de la URL en el archivo [id].js accediendo a router.query.id.

La parte dinámica también puede ser sólo una parte de la URL, como post-[id].js.

Así que vamos a seguir adelante y aplicar todas esas cosas en la práctica.

Crea el archivo pages/blog/[id].js:

import { useRouter } from 'next/router'

export default () => {
  const router = useRouter()

  return (
    <>
      <h1>Blog post</h1>
      <p>Post id: {router.query.id}</p>
    </>
  )
}
Uso del Hook useRouter

Ahora si vas al router http://localhost:3000/blog/test, deberías ver esto:

Componente renderizado Blog con uso enrutador
Componente renderizado Blog con uso enrutador

Podemos utilizar este parámetro id para recoger el artículo de una lista de articulos. De una base de datos, por ejemplo. Para mantener las cosas simples vamos a añadir un archivoposts.json en la carpeta raíz del proyecto:

{
  "test": {
    "title": "test post",
    "content": "Hey some post content"
  },
  "second": {
    "title": "second post",
    "content": "Hey this is the second post content"
  }
}
Json con los diferentes artículos o posts

Ahora podemos importarlo y buscar el puesto a partir de la clave id:

import { useRouter } from 'next/router'
import posts from '../../posts.json'

export default () => {
  const router = useRouter()

  const post = posts[router.query.id]

  return (
    <>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </>
  )
}
Código con los artículos

La recarga de la página debería mostrarnos este resultado:

Componente con artículo ejemplo
Componente con artículo ejemplo

Pero no es así. En su lugar, obtenemos un error en la consola, y un error en el navegador también:

Error con propiedad title del artículo esta en undefined
Error con propiedad title del artículo esta en undefined

¿Por qué? Porque... durante el renderizado, cuando se inicializa el componente, los datos no están ahí todavía. Veremos cómo proporcionar los datos al componente con getInitialProps en la próxima lección.

Por ahora, añade una pequeña comprobación if (!post) return <p></p> antes de devolver el JSX:

import { useRouter } from 'next/router'
import posts from '../../posts.json'

export default () => {
  const router = useRouter()

  const post = posts[router.query.id]
  if (!post) return <p></p>

  return (
    <>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </>
  )
}
Comprobación en caso de que un post sea indefinido

Ahora las cosas deberían funcionar. Inicialmente el componente se muestra sin la información dinámica router.query.id. Después de la representación, Next.js activa una actualización con el valor de la consulta y la página muestra la información correcta.

Y si ves el código fuente, hay una etiqueta <p>vacía en el HTML

Comprobación de código fuente artículo undefined
Comprobación de código fuente artículo undefined

Pronto solucionaremos este problema que no implementa SSR y esto perjudica tanto a los tiempos de carga para nuestros usuarios, SEO y el compartir en redes sociales como ya comentamos.

Podemos completar el ejemplo del blog listando esas entradas en pages/blog.js:

import posts from '../posts.json'

const Blog = () => (
  <div>
    <h1>Blog</h1>

    <ul>
      {Object.entries(posts).map((value, index) => {
        return <li key={index}>{value[1].title}</li>
      })}
    </ul>
  </div>
)

export default Blog
Componente blog listando articulos

Y podemos enlazarlos a las páginas individuales de los posts, importando Link desde next/link usándolo dentro del bucle de posts:

import Link from 'next/link'
import posts from '../posts.json'

const Blog = () => (
  <div>
    <h1>Blog</h1>

    <ul>
      {Object.entries(posts).map((value, index) => {
        return (
          <li key={index}>
            <Link href='/blog/[id]' as={'/blog/' + value[0]}>
              <a>{value[1].title}</a>
            </Link>
          </li>
        )
      })}
    </ul>
  </div>
)

export default Blog
Componente blog enlazando articulos

Búsqueda previa

Anteriormente mencioné como el componente Link Next.js puede ser usado para crear enlaces entre 2 páginas cuando lo usas en Next.js, maneja de forma transparente el enrutamiento frontend por nosotros, así que cuando un usuario hace clic en un enlace, frontend se encarga de mostrar la nueva página sin desencadenar un nuevo ciclo de petición y respuesta cliente/servidor, como sucede normalmente con las páginas web.

Hay otra cosa que Next.js hace por ti cuando utilizas Link.

Tan pronto como un elemento envuelto dentro de <Link> aparece en la ventana gráfica (lo que significa que es visible para el usuario del sitio web), Next.js precarga la URL a la que apunta, siempre y cuando sea un enlace local (en su sitio web), haciendo que la aplicación sea súper rápida para el espectador.

Este comportamiento sólo se activa en modo de producción (hablaremos de esto en profundidad más adelante), lo que significa que tienes que detener la aplicación si la estás ejecutando con npm run dev, compilar tu bundle de producción con npm run build y ejecutarlo con npm run start en su lugar.

Usando el inspector de Network(red) en DevTools te darás cuenta de que cualquier enlace por encima del pliegue, en la carga de la página, comienza la precarga tan pronto como el evento load se ha disparado en tu página (se dispara cuando la página está completamente cargada, y sucede después del evento DOMContentLoaded).

Cualquier otra etiqueta Link que no esté en la ventana gráfica será precargada cuando el usuario haga scroll npm run dev.

La precarga es automática en conexiones de alta velocidad (Wifi y 3g+), a menos que el navegador envíe el encabezado HTTP Save-Data.

Usted puede optar por no precargar instancias de Link individuales estableciendo la prop prefetch a false:

<Link href="/a-link" prefetch={false}>
  <a>A link</a>
</Link>
Precarga en falso en componente Link

Uso del router para detectar el enlace activo

Una característica muy importante cuando se trabaja con enlaces, es determinar cuál es la URL actual y en particular asignar una clase al enlace activo, para que podamos darle un estilo diferente a los demás.

Esto es especialmente útil en la cabecera de su sitio, por ejemplo.

El componente Link por defecto de Next.js que se ofrece en next/link no hace esto automáticamente por nosotros.

Podemos crear un componente Link nosotros mismos, y lo almacenamos en un archivo Link.js en la carpeta Components e importamos eso en lugar del predeterminado next/link.

En este componente, primero importaremos React de react, Link de next/link y el hook useRouter de next/router.

Dentro del componente determinamos si el nombre de la ruta actual coincide con la href  prop del componente y si es así añadimos la clase selected a los hijos.

Finalmente devolvemos estos niños con la clase actualizada, utilizando React.cloneElement():

import React from 'react'
import Link from 'next/link'
import { useRouter } from 'next/router'

export default ({ href, children }) => {
  const router = useRouter()

  let className = children.props.className || ''
  if (router.pathname === href) {
    className = `${className} selected`
  }

  return <Link href={href}>{React.cloneElement(children, { className })}</Link>
}
Detectar enlace activo

Uso de next/router

Ya hemos visto cómo utilizar el componente Link para manejar el enrutamiento de forma declarativa en las aplicaciones Next.js.

Es realmente práctico gestionar el enrutamiento en JSX, pero a veces necesitas activar un cambio de enrutamiento mediante programación.

En este caso, puedes acceder directamente al enrutador Next.js, proporcionado en el paquete next/router, y llamar a su método push().

He aquí un ejemplo de acceso al enrutador:

import { useRouter } from 'next/router'

export default () => {
  const router = useRouter()
  //...
}
Uso Hook useRouter

Una vez que obtenemos el objeto router invocando useRouter(), podemos utilizar sus métodos.

Este es el router del lado del cliente, por lo que los métodos sólo deben ser utilizados en el código frontend. La forma más fácil de asegurar esto es envolver las llamadas en el hook useEffect() de React o dentro de componentDidMount()en los componentes con estado de React.

Las más utilizadas son push() y prefetch().

push() nos permite desencadenar programáticamente un cambio de URL, en el frontend:

router.push('/login')
router API - push

prefetch() nos permite precargar una URL programada, lo que resulta útil cuando no disponemos de una etiqueta Link que gestione automáticamente la precarga por nosotros:

router.prefetch('/login')
router API - prefetch

Ejemplo completo:

import { useRouter } from 'next/router'

export default () => {
  const router = useRouter()

  useEffect(() => {
    router.prefetch('/login')
  })
}
router API - prefetch en componente

También puedes utilizar el router para escuchar eventos de cambio de ruta.

Proporcionar datos a los componentes mediante getInitialProps

En el capítulo anterior tuvimos un problema con la generación dinámica de la página de publicación, porque el componente requería algunos datos por adelantado y cuando intentamos obtener los datos del archivo JSON:

import { useRouter } from 'next/router'
import posts from '../../posts.json'

export default () => {
  const router = useRouter()

  const post = posts[router.query.id]

  return (
    <>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </>
  )
}
Componente Article - Post

Obtenemos este error:

Screen-Shot-2019-11-05-at-18.18.17-1
Error datos undefined

¿Cómo lo solucionamos? ¿Y cómo hacemos que SSR funcione para rutas dinámicas?

Debemos dotar al componente de props, utilizando una función especial llamada getInitialProps() que se adjunta al componente.

Para ello, primero nombramos el componente:

const Post = () => {
  //...
}

export default Post
Componente Post

Entonces le añadimos la función:

const Post = () => {
  //...
}

Post.getInitialProps = () => {
  //...
}

export default Post
Adición de función 

Esta función obtiene como argumento un objeto que contiene varias propiedades. En concreto, lo que nos interesa ahora es que obtenga el objeto query, el que usamos anteriormente para obtener el id del post.

Así que podemos obtenerlo usando la sintaxis de desestructuración de objetos:

Post.getInitialProps = ({ query }) => {
  //...
}
Desestructuración de Props

Ahora podemos devolver el post desde esta función:

Post.getInitialProps = ({ query }) => {
  return {
    post: posts[query.id]
  }
}
getInitialProps - 

Y también podemos eliminar la importación de useRouter y obtenemos el post de la propiedad props pasada al componente Post:

import posts from '../../posts.json'

const Post = props => {
  return (
    <div>
      <h1>{props.post.title}</h1>
      <p>{props.post.content}</p>
    </div>
  )
}

Post.getInitialProps = ({ query }) => {
  return {
    post: posts[query.id]
  }
}

export default Post
Obtención de props basados en getInitialProps

Ahora no habrá ningún error, el SSR estará funcionando como se esperaba, como se puede ver comprobando la vista fuente:

Código fuente con getInitialProps
Código fuente con getInitialProps

La función getInitialProps se ejecutará en el lado del servidor, pero también en el lado del cliente, cuando naveguemos a una nueva página utilizando el componente Link como hemos hecho.

Es importante tener en cuenta que getInitialProps obtiene el objeto de contexto que recibe, además del objeto de query(consulta) estas otras propiedades:

  • pathname: la sección path de la URL
  • asPath: Cadena de texto de la ruta real (incluida la consulta) que se muestra en el navegador

que en el caso de llamar ahttp://localhost:3000/blog/test dará como resultado respectivamente:

  • /blog/[id]
  • /blog/test

Y en el caso del renderizado del lado del servidor, también recibirá:

  • req: el objeto de solicitud HTTP
  • res: el objeto de respuesta HTTP
  • err: un objeto de error

req y res te resultarán familiares si has codificado con Node.js.

CSS

¿Cómo damos estilo a los componentes React en Next.js?

Tenemos mucha libertad, porque podemos usar la librería de nuestra preferencia.

Pero Next.js viene con styled-jsx incorporado, porque es una librería construida por la misma gente que trabaja en Next.js.

Se trata de una biblioteca muy interesante que nos proporciona CSS de ámbito limitado, lo que resulta muy útil para el mantenimiento, ya que el CSS sólo afecta al componente al que se aplica.

Creo que este es un gran enfoque para escribir CSS, sin la necesidad de aplicar bibliotecas adicionales o preprocesadores que añaden complejidad.

Para añadir CSS a un componente React en Next.js lo insertamos dentro de un snippet en el JSX, que empieza por

<style jsx>{`
Snippet JSX de estilo de apertura

y termina con

`}</style>
Snippet JSX de estilo de cierre

Dentro de estos extraños bloques escribimos CSS plano, como haríamos en un archivo .css:

<style jsx>{`
  h1 {
    font-size: 3rem;
  }
`}</style>

Lo escribes dentro del JSX, así:

const Index = () => (
  <div>
		<h1>Home page</h1>

		<style jsx>{`
		  h1 {
		    font-size: 3rem;
		  }
		`}</style>
  </div>
)

export default Index

Dentro del bloque podemos utilizar la interpolación para cambiar dinámicamente los valores. Por ejemplo, aquí suponemos que el componente padre pasa una propiedad de size y la utilizamos en el bloque styled-jsx:

const Index = props => (
  <div>
		<h1>Home page</h1>

		<style jsx>{`
		  h1 {
		    font-size: ${props.size}rem;
		  }
		`}</style>
  </div>
)
Componente Index con estilos en el título

Si desea aplicar un CSS de forma global, no limitado a un componente, añada la palabra clave global a la etiqueta style:

<style jsx global>{`
body {
  margin: 0;
}
`}</style>
Estilos globales

Si quieres importar un archivo CSS externo en un componente Next.js, primero tienes que instalar @zeit/next-css:

npm install @zeit/next-css
Paquete para componente externo

Luego creas un archivo de configuración en la raíz del proyecto, llamado next.config.js, con este contenido:

const withCSS = require('@zeit/next-css')
module.exports = withCSS()
Configuración Paquete zeit

Tras reiniciar la aplicación Next, ya puedes importar CSS como haces normalmente con las bibliotecas o componentes JavaScript:

import '../style.css'
Importar CSS debido a zeit

También puede importar un archivo SASS directamente, utilizando en su lugar la biblioteca @zeit/next-sass.

Rellenar la etiqueta head con etiquetas personalizadas

Desde cualquier componente de página Next.js, puede añadir información a la cabecera de la página.

Esto es útil cuando:

  • Desea personalizar el título de la página
  • Desea cambiar una etiqueta meta

¿Cómo puedes hacerlo?

Dentro de cada componente puedes importar el componente Head desde next/head e incluirlo en la salida JSX de tu componente:

import Head from 'next/head'

const House = props => (
  <div>
    <Head>
      <title>The page title</title>
    </Head>
    {/* Resto de JSX */}
  </div>
)

export default House
Modificar etiqueta Head

Puedes añadir cualquier etiqueta HTML que quieras que aparezca en la sección <head> de la página.

Al montar el componente, Next.js se asegurará de que las etiquetas dentro de Head se añadan al encabezado de la página. Lo mismo al desmontar el componente, Next.js se encargará de eliminar esas etiquetas.

Añadir un componente envoltorio(wrapper component)

Todas las páginas de tu sitio tienen más o menos el mismo aspecto. Hay una ventana chrome, una capa base común y sólo quieres cambiar lo que hay dentro.

Hay una barra de navegación, una barra lateral, y luego el contenido real.

¿Cómo se construye este sistema en Next.js?

Hay dos maneras. Una es usando un Componente de Orden Superior(HOC), creando un componente components/Layout.js:

export default Page => {
  return () => (
    <div>
      <nav>
        <ul>....</ul>
      </hav>
      <main>
        <Page />
      </main>
    </div>
  )
}
componente Page

Ahí podemos importar componentes separados para el encabezado y/o la barra lateral y también podemos añadir todo el CSS que necesitemos.

Y lo usas en cada página así:

import withLayout from '../components/Layout.js'

const Page = () => <p>Here's a page!</p>

export default withLayout(Page)
Componente de Orden Superior

Pero encontré que esto funciona sólo para casos simples, donde no es necesario llamar a getInitialProps() en una página.

¿Por qué?

Porque getInitialProps() sólo se llama en el componente página. Pero si exportamos el componente de orden superior(HOC) withLayout() desde una página, Page.getInitialProps() no es llamado. withLayout.getInitialProps() sí lo sería.

Para evitar complicar innecesariamente nuestra base de código, el enfoque alternativo es utilizar props:

export default props => (
  <div>
    <nav>
      <ul>....</ul>
    </hav>
    <main>
      {props.content}
    </main>
  </div>
)

y en nuestras páginas ahora lo usamos así:

import Layout from '../components/Layout.js'

const Page = () => (
  <Layout content={(
    <p>Here's a page!</p>
  )} />
)
Pasar componente mediante Props

Este enfoque nos permite utilizar getInitialProps() dentro de nuestro componente de página, con el único inconveniente de tener que escribir el componente JSX dentro del prop content:

import Layout from '../components/Layout.js'

const Page = () => (
  <Layout content={(
    <p>Here's a page!</p>
  )} />
)

Page.getInitialProps = ({ query }) => {
  //...
}

API Routes

Además de crear rutas de página(page routes), lo que significa que las páginas se sirven al navegador como páginas web, Next.js puede crear rutas API(API routes).

Esta es una característica muy interesante porque significa que Next.js puede utilizarse para crear un frontend para datos que son almacenados y recuperados por el propio Next.js, transfiriendo JSON a través de peticiones fetch.

Las rutas API viven bajo la carpeta /pages/api/ y están mapeadas al endpoint /api.

Esta característica es muy útil a la hora de crear aplicaciones.

En esas rutas, escribimos código Node.js (en lugar de código React). Es un cambio de paradigma, pasas del frontend al backend, pero muy fluido.

Imagina que tienes un archivo /pages/api/comments.js, cuyo objetivo es devolver los comentarios de una entrada de blog como JSON.

Digamos que tienes una lista de comentarios almacenados en un archivo comments.json:

[
  {
    "comment": "First"
  },
  {
    "comment": "Nice post"
  }
]
JSON de artículos

He aquí un ejemplo de código, que devuelve al cliente la lista de comentarios:

import comments from './comments.json'

export default (req, res) => {
  res.status(200).json(comments)
}
Retornar comentarios en JSON

Esto escuchará en la URL /api/comments para peticiones GET, puedes intentar llamarlo usando tu navegador:

JSON retornado del servidor
JSON retornado del servidor

Las rutas del API pueden utilizar enrutamiento dinámico como las páginas, use la sintaxis [] para crear una ruta de API dinámica, como /pages/api/comments/[id].js que recuperará los comentarios específicos de un id de entrada.

Dentro de [id].js puedes recuperar el valor del id buscándolo dentro del objeto req.query:

import comments from '../comments.json'

export default (req, res) => {
  res.status(200).json({ post: req.query.id, comments })
}

Aquí puedes ver el código anterior en acción:

SId de los artículos
Id de los artículos

En páginas dinámicas, tendrías que importar useRouter de next/router, luego obtener el objeto router usando const router = useRouter(), luego podríamos obtener el valor id usando router.query.id.

En el lado del servidor todo es más fácil, ya que la consulta se adjunta al objeto de solicitud.

Si haces una petición POST, todo funciona de la misma manera - todo va a través de esa exportación por defecto.

Para separar POST de GET y otros métodos HTTP (PUT, DELETE), busca el valor req.method:

export default (req, res) => {
  switch (req.method) {
    case 'GET':
      //...
      break
    case 'POST':
      //...
      break
    default:
      res.status(405).end() //Method Not Allowed
      break
  }
}

Además de req.query y req.method que ya vimos, tenemos acceso a las cookies haciendo referencia a req.cookies, el cuerpo de la petición en req.body.

Bajo el capó, todo esto es impulsado por Micro, una biblioteca que impulsa microservicios HTTP asíncronos, hecha por el mismo equipo que construyó Next.js.

Puedes hacer uso de cualquier middleware Micro en nuestras rutas API para añadir más funcionalidad.

Ejecutar código sólo en el lado del servidor o en el lado del cliente

En tus componentes de página, puedes ejecutar código sólo en el lado del servidor o en el lado del cliente, comprobando la propiedad window.

Esta propiedad sólo existe dentro del navegador, puedes comprobar

if (typeof window === 'undefined') {

}

y añadir el código del lado del servidor en ese bloque.

Del mismo modo, puede ejecutar código del lado del cliente sólo comprobando

if (typeof window !== 'undefined') {

}

Consejo JS: Usamos el operador typeof aquí porque no podemos detectar que un valor sea undefined de otra forma. No podemos hacer if (window === undefined) porque obtendríamos un error de ejecución "window is not defined".

Next.js, como optimización en tiempo de compilación, también elimina de los paquetes el código que utiliza esas comprobaciones. Un paquete del lado del cliente no incluirá el contenido envuelto en un bloque if (typeof window === 'undefined') {}.

Despliegue de la versión de producción

El despliegue de una aplicación siempre se deja para el final en los tutoriales.

Aquí quiero introducirlo antes, sólo porque es tan fácil desplegar una aplicación Next.js que podemos sumergirnos en ello ahora, y luego pasar a otros temas más complejos más adelante.

Recuerda que en el capítulo "Cómo instalar Next.js" te dije que añadieras esas 3 líneas a la sección del script package.json:

"scripts": {
  "dev": "next",
  "build": "next build",
  "start": "next start"
}

Hasta ahora usábamos npm run dev, para llamar al comando next instalado localmente en node_modules/next/dist/bin/next. Esto inició el servidor de desarrollo, que nos proporcionó mapas de código fuente(sourcemaps) y recarga de código en caliente(hot code reloading), dos características muy útiles durante la depuración.

El mismo comando puede ser invocado para construir el sitio web pasando la bandera build, ejecutando npm run build. A continuación, el mismo comando se puede utilizar para iniciar la aplicación de producción pasando la bandera start, ejecutando npm run start.

Esos 2 comandos son los que debemos invocar para desplegar con éxito la versión de producción de nuestro sitio localmente. La versión de producción está altamente optimizada y no viene con mapas de fuentes(sourcemaps) y otras cosas como la recarga de código en caliente(hot code reloading) que no sería beneficioso para nuestros usuarios finales.

Por lo tanto, vamos a crear un despliegue de producción de nuestra aplicación. Construirlo usando:

npm run build
Construir paquete de aplicaciones(app bundle)
Construir paquete de aplicaciones(app bundle)

La salida del comando nos dice que algunas rutas (/ y /blog están ahora prerenderizadas como HTML estático, mientras que /blog/[id] será servido por el backend Node.js.

A continuación, puede ejecutar npm run start para iniciar el servidor de producción localmente:

npm run start
npm run start
npm run start

Visitar http://localhost:3000 nos muestra la versión de producción de la aplicación, localmente.

Despliegue en Now

En el capítulo anterior desplegamos la aplicación Next.js localmente.

Cómo la desplegamos en un servidor web real, para que otras personas puedan acceder a ella?

Una de las formas más sencillas de desplegar una aplicación Next es a través de la plataforma Now creada por Zeit, la misma empresa que creó el proyecto Open Source Next.js. Puedes utilizar Now para desplegar aplicaciones Node.js, sitios web estáticos y mucho más.

Ahora hace que el paso de despliegue y distribución de una aplicación sea muy, muy sencillo y rápido, y además de aplicaciones Node.js, también admite el despliegue de Go, PHP, Python y otros lenguajes.

Puedes pensar en ella como en la "nube", ya que no sabes realmente dónde se desplegará tu aplicación, pero sabes que tendrás una URL a la que podrás acceder.

Now es gratuito para empezar a usarlo, con un generoso plan gratuito que actualmente incluye 100 GB de alojamiento, 1000 invocaciones de funciones serverless, 1000 invocaciones al mes, 100 GB de ancho de banda al mes y una ubicación CDN. La página de precios ayuda a hacerse una idea de los costes si necesita más.

La mejor manera de empezar a usar Now es utilizando la CLI oficial de Now:

npm install -g now

Una vez que el comando esté disponible, ejecute

now login

La aplicación te pide el correo electrónico.

Si aún no se ha registrado, cree una cuenta en https://zeit.co/signup antes de continuar y, a continuación, añada su dirección de correo electrónico al cliente CLI.

Una vez hecho esto, desde la carpeta raíz del proyecto Next.js ejecuta

now

y la aplicación se desplegará instantáneamente en la nube de Now y se le proporcionará la URL única de la aplicación:

Screen-Shot-2019-11-06-at-14.21.09

Una vez ejecutado el programa now, la aplicación se despliega en una URL aleatoria bajo el dominio now.sh.

Podemos ver 3 URLs diferentes en la salida dada en la imagen:

¿Por qué tantos?
La primera es la URL que identifica el despliegue. Cada vez que despleguemos la aplicación, esta URL cambiará.

Usted puede probar inmediatamente cambiando algo en el código del proyecto y ejecutar de nuevo el comando now:

Proyecto desplegado en now
Proyecto desplegado en now

Las otras 2 URLs no cambiarán. La primera es aleatoria, la segunda es el nombre de tu proyecto (que por defecto es la carpeta del proyecto actual, tu nombre de cuenta y luego now.sh.

Si visitas la URL, verás la aplicación desplegada en producción.

Aplicación en producción
Aplicación en producción

Usted puede configurar Now para servir el sitio a su propio dominio personalizado o subdominio, pero no voy a adentrarme en eso ahora.

El subdominio now.sh es suficiente para nuestras pruebas.

Análisis de los paquetes de aplicaciones

Next nos proporciona una forma de analizar los paquetes de código que se generan.

Abre el archivo package.json de la app y en la sección scripts añade estos 3 nuevos comandos:

"analyze": "cross-env ANALYZE=true next build",
"analyze:server": "cross-env BUNDLE_ANALYZE=server next build",
"analyze:browser": "cross-env BUNDLE_ANALYZE=browser next build"
Comandos para analizar paquetes

Asi:

{
  "name": "firstproject",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "next",
    "build": "next build",
    "start": "next start",
    "analyze": "cross-env ANALYZE=true next build",
    "analyze:server": "cross-env BUNDLE_ANALYZE=server next build",
    "analyze:browser": "cross-env BUNDLE_ANALYZE=browser next build"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "next": "^9.1.2",
    "react": "^16.11.0",
    "react-dom": "^16.11.0"
  }
}
package.json

y luego instale esos 2 paquetes:

npm install --dev cross-env @next/bundle-analyzer

Crea un archivo next.config.js en la raíz del proyecto, con este contenido:

const withBundleAnalyzer = require('@next/bundle-analyzer')({
  enabled: process.env.ANALYZE === 'true'
})

module.exports = withBundleAnalyzer({})

Ahora ejecute el comando

npm run analyze
Screen-Shot-2019-11-06-at-16.12.40

Esto debería abrir 2 páginas en el navegador. Una para los paquetes cliente y otra para los paquetes servidor:

Análisis de paquetes del cliente
Análisis de paquetes del cliente
Análisis de paquetes del servidor
Análisis de paquetes del servidor

Esto es increíblemente útil. Puedes inspeccionar lo que está ocupando más espacio en los paquetes, y también puedes utilizar la barra lateral para excluir paquetes, para una visualización más fácil de los más pequeños:

Filtro de resultados de análisis paquetes
Filtro de resultados de análisis paquetes

Módulos de carga diferida

Poder analizar visualmente un bundle es genial, porque podemos optimizar nuestra aplicación de una manera adecuada.

Digamos que necesitamos cargar la librería Moment en nuestro blog posts. Ejecuta:

npm install moment

para incluirlo en el proyecto.

Ahora vamos a simular que lo necesitamos en dos rutas diferentes: /blog y /blog/[id].

Lo importamos en pages/blog/[id].js:

import moment from 'moment'

...

const Post = props => {
  return (
    <div>
      <h1>{props.post.title}</h1>
      <p>Published on {moment().format('dddd D MMMM YYYY')}</p>
      <p>{props.post.content}</p>
    </div>
  )
}

Sólo estoy añadiendo la fecha de hoy, como ejemplo.

Esto incluirá Moment.js en el paquete de la página de la entrada del blog, como puedes ver ejecutando npm run analyze:

Screen-Shot-2019-11-06-at-17.56.14

Observa que ahora tenemos una entrada roja en /blog/[id], ¡la ruta a la que añadimos Moment.js!

Ha pasado de ~1kB a 350kB, una gran cosa. Y esto se debe a que la propia biblioteca Moment.js ocupa 349kB.

La visualización de bundles del cliente nos muestra ahora que el bundle más grande es el de la página, que antes era muy poco. Y el 99% de su código es Moment.js.

Screen-Shot-2019-11-06-at-17.55.50

Cada vez que cargamos una entrada de blog vamos a tener todo este código transferido al cliente. Lo cual no es lo ideal.

Una solución sería buscar una biblioteca con un tamaño más pequeño, como Moment.js no es conocido por ser ligero (especialmente fuera de la caja con todos los locales incluidos), pero vamos a suponer por el bien del ejemplo que debemos usarlo.

Lo que podemos hacer en su lugar es separar todo el código de Moment en un paquete separado.

¿Cómo? En lugar de importar Moment a nivel de componente, realizamos una importación asíncrona dentro de getInitialProps y calculamos el valor a enviar al componente.

Recuerda que no podemos devolver objetos complejos dentro del objeto devuelto por getInitialProps(), así que calculamos la fecha dentro de él:

import posts from '../../posts.json'

const Post = props => {
  return (
    <div>
      <h1>{props.post.title}</h1>
      <p>Published on {props.date}</p>
      <p>{props.post.content}</p>
    </div>
  )
}

Post.getInitialProps = async ({ query }) => {
  const moment = (await import('moment')).default()
  return {
    date: moment.format('dddd D MMMM YYYY'),
    post: posts[query.id]
  }
}

export default Post

¿Ves esa llamada especial a .default() después de await import? Es necesaria para hacer referencia a la exportación por defecto en una importación dinámica (véase https://v8.dev/features/dynamic-import)

Ahora si ejecutamos npm run analyze de nuevo, podemos ver esto:

Screen-Shot-2019-11-06-at-18.00.22

Nuestro bundle /blog/[id] vuelve a ser muy pequeño, ya que Moment se ha trasladado a su propio archivo bundle, cargado por separado por el navegador.

¿Qué hacer a partir de ahora?

Hay mucho más que saber sobre Next.js. No he hablado de la gestión de sesiones de usuario con login, serverless, gestión de bases de datos, etc.

El objetivo de este Manual no es enseñártelo todo, sino introducirte, poco a poco, el potencial de Next.js.

El siguiente paso que recomiendo es leer detenidamente la documentación oficial de Next.js para obtener más información sobre todas las características y funcionalidades de las que no he hablado y echar un vistazo a todas las funcionalidades adicionales introducidas por los plugins de Next.js, algunas de las cuales son bastante sorprendentes.

Puedes ponerte en contacto conmigo en Twitter @flaviocopes.

Visite también mi sitio web, flaviocopes.com.

Nota: puede descargar una versión PDF / ePub / Mobi de este tutorial para poder leerlo sin conexión.