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
- Introducción
- Principales características de Next.js
- Next.js vs Gatsby vs
create-react-app
- Cómo instalar Next.js
- Revisar código fuente para confirmar que SSR funciona
- Los paquetes de aplicaciones
- ¿Qué es ese icono en la parte inferior derecha?
- Instalar React DevTools
- Otras técnicas de depuración que puedes utilizar
- Añadir una segunda página al sitio web
- Enlazar las dos páginas
- Contenido dinámico con el router
- Prefetching(Búsqueda previa)
- Uso del router para detectar el enlace activo
- Uso de
next/router
- Alimentación de datos a los componentes mediante
getInitialProps()
- CSS
- Rellenar la etiqueta head con etiquetas personalizadas
- Añadir un componente envolvente (Wrapper component)
- Rutas API
- Ejecutar código en el lado del servidor o en el lado del cliente
- Despliegue de la versión de producción
- Despliegue en Now
- Análisis de los paquetes de aplicaciones
- Módulos de carga lenta
- 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:
- Cómo instalar Node.js
- Cómo actualizar Node.js
- Introducción al gestor de paquetes npm
- Tutorial de Unix Shell
- Cómo usar el terminal de macOS
- El intérprete de comandos Bash
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:
y puedes ejecutar inmediatamente la aplicación de ejemplo ejecutando npm run dev
:
Y aquí está el resultado en http://localhost:3000:
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:
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.
Ahora instala Next y React:
npm install next react react-dom
Su carpeta de proyecto debe tener ahora 2 archivos:
package.json
(ver mi tutorial sobre ello)package-lock.json
(ver mi tutorial sobre package-lock)
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.
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.
Abra http://localhost:3000 en su navegador para verlo.
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).
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:
Empecemos por poner el código en un formateador HTML para formatearlo mejor de modo que los humanos tengamos más posibilidades de entenderlo:
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:
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?
Si pasas el ratón por encima, dirá "Página prerrenderizada":
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:
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.
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:
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.
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
:
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:
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.
He mostrado todas las capturas de pantalla usando Chrome, pero React Developer Tools funciona de la misma manera en 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:
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:
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
:
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:
y esto es lo que nos dijo el terminal:
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.
También podemos simplemente exportar una función anónima desde blog.js
:
o si prefiere la sintaxis de funciones sin flechas:
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
:
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
:
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:
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:
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:
y luego lo usamos para envolver nuestro enlace, así:
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:
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
:
y una vez que tenemos useRouter
, instanciamos el objeto router usando:
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
:
Ahora si vas al router http://localhost:3000/blog/test
, deberías ver esto:
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:
Ahora podemos importarlo y buscar el puesto a partir de la clave id
:
La recarga de la página debería mostrarnos este resultado:
Pero no es así. En su lugar, obtenemos un error en la consola, y un error en el navegador también:
¿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:
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
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
:
Y podemos enlazarlos a las páginas individuales de los posts, importando Link
desde next/link
usándolo dentro del bucle de posts:
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
:
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()
:
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:
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:
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:
Ejemplo completo:
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:
Obtenemos este error:
¿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:
Entonces le añadimos la 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:
Ahora podemos devolver el post desde esta función:
Y también podemos eliminar la importación de useRouter
y obtenemos el post de la propiedad props
pasada al componente Post
:
Ahora no habrá ningún error, el SSR estará funcionando como se esperaba, como se puede ver comprobando la vista fuente:
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ónpath
de la URLasPath
: 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 HTTPres
: el objeto de respuesta HTTPerr
: 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
y termina con
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
:
Si desea aplicar un CSS de forma global, no limitado a un componente, añada la palabra clave global
a la etiqueta style
:
Si quieres importar un archivo CSS externo en un componente Next.js, primero tienes que instalar @zeit/next-css
:
Luego creas un archivo de configuración en la raíz del proyecto, llamado next.config.js
, con este contenido:
Tras reiniciar la aplicación Next, ya puedes importar CSS como haces normalmente con las bibliotecas o componentes JavaScript:
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:
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
:
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í:
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í:
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
:
He aquí un ejemplo de código, que devuelve al cliente la lista de comentarios:
Esto escuchará en la URL /api/comments
para peticiones GET, puedes intentar llamarlo usando tu navegador:
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:
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
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
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:
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:
- https://firstproject-2pv7khwwr.now.sh
- https://firstproject-sepia-ten.now.sh
- https://firstproject.flaviocopes.now.sh
¿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
:
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.
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:
Asi:
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
Esto debería abrir 2 páginas en el navegador. Una para los paquetes cliente y otra para los paquetes 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:
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
:
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.
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:
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.