Análisis de compilaciones voluminosas con Netlify y Next.js
Publicado: 2022-03-10Este artículo ha sido amablemente apoyado por nuestros queridos amigos de Netlify, que son un grupo diverso de increíbles talentos de todo el mundo y ofrecen una plataforma para desarrolladores web que multiplica la productividad. ¡Gracias!
Uno de los mayores inconvenientes de trabajar con sitios web generados de forma estática son las compilaciones cada vez más lentas a medida que crece la aplicación. Este es un problema inevitable al que se enfrenta cualquier pila en algún momento y puede surgir desde diferentes puntos dependiendo del tipo de producto con el que esté trabajando.
Por ejemplo, si su aplicación tiene varias páginas (vistas, rutas) al generar el artefacto de implementación, cada una de esas rutas se convierte en un archivo. Luego, una vez que ha llegado a miles, comienza a preguntarse cuándo puede implementar sin necesidad de planificar con anticipación. Este escenario es común en las plataformas de comercio electrónico o blogs, que ya son una gran parte de la web, pero no toda. Sin embargo, las rutas no son el único cuello de botella posible.
Una aplicación con muchos recursos también llegará eventualmente a este punto de inflexión. Muchos generadores estáticos llevan a cabo la optimización de activos para garantizar la mejor experiencia de usuario. Sin optimizaciones de compilación (construcciones incrementales, almacenamiento en caché, las abordaremos pronto), esto eventualmente también se volverá inmanejable: piense en revisar todas las imágenes en un sitio web: cambiar el tamaño, eliminar y/o crear nuevos archivos una y otra vez. Y una vez que todo esté hecho: recuerde que Jamstack sirve nuestras aplicaciones desde los bordes de la red de entrega de contenido . Por lo tanto, todavía tenemos que mover las cosas desde el servidor en el que se compilaron hasta los bordes de la red.
Además de todo eso, también hay otro hecho: los datos a menudo son dinámicos, lo que significa que cuando creamos nuestra aplicación y la implementamos, puede demorar unos segundos, unos minutos o incluso una hora. Mientras tanto, el mundo sigue girando, y si estamos obteniendo datos de otro lugar, nuestra aplicación seguramente quedará obsoleta. ¡Inaceptable! ¡Construye de nuevo para actualizar!
Cree una vez, actualice cuando sea necesario
Resolver Bulky Builds ha sido lo más importante para prácticamente todas las plataformas, marcos o servicios de Jamstack durante un tiempo. Muchas soluciones giran en torno a compilaciones incrementales. En la práctica, esto significa que las compilaciones serán tan voluminosas como las diferencias que conllevan con respecto a la implementación actual.
Sin embargo, definir un algoritmo diff no es tarea fácil. Para que el usuario final realmente se beneficie de esta mejora, se deben considerar estrategias de invalidación de caché. En pocas palabras: no queremos invalidar el caché de una página o un activo que no ha cambiado.
Next.js creó la regeneración estática incremental ( ISR ). En esencia, es una forma de declarar para cada ruta con qué frecuencia queremos que se reconstruya. Debajo del capó, simplifica mucho el trabajo del lado del servidor. Porque cada ruta (dinámica o no) se reconstruirá a sí misma en un marco de tiempo específico, y encaja perfectamente en el axioma de Jamstack de invalidar el caché en cada compilación. Piense en ello como el encabezado max-age
pero para rutas en su aplicación Next.js.
Para iniciar su aplicación, ISR está a solo una propiedad de configuración de distancia. En su componente de ruta (dentro del directorio /pages
) vaya a su método getStaticProps
y agregue la clave de revalidate
al objeto de retorno:
export async function getStaticProps() { const { limit, count, pokemons } = await fetchPokemonList() return { props: { limit, count, pokemons, }, revalidate: 3600 // seconds } }
El fragmento anterior se asegurará de que mi página se reconstruya cada hora y busque más Pokémon para mostrar.
Todavía recibimos compilaciones masivas de vez en cuando (al emitir una nueva implementación). Pero esto nos permite desacoplar el contenido del código, al mover el contenido a un Sistema de gestión de contenido (CMS) podemos actualizar la información en unos segundos, independientemente del tamaño de nuestra aplicación. ¡Adiós a los webhooks para actualizar errores tipográficos!
Constructores bajo demanda
Netlify lanzó recientemente On-Demand Builders, que es su enfoque para admitir ISR para Next.js, pero también funciona en marcos que incluyen Eleventy y Nuxt. En la sesión anterior, establecimos que ISR fue un gran paso hacia tiempos de compilación más cortos y abordó una parte significativa de los casos de uso. Sin embargo, las advertencias estaban ahí:
- Compilaciones completas sobre implementación continua.
La etapa incremental ocurre solo después de la implementación y para los datos. No es posible enviar el código de forma incremental - Las compilaciones incrementales son producto del tiempo.
La memoria caché se invalida en función del tiempo. Por lo tanto, pueden ocurrir compilaciones innecesarias o las actualizaciones necesarias pueden demorar más según el período de revalidación establecido en el código.
La nueva infraestructura de implementación de Netlify permite a los desarrolladores crear una lógica para determinar qué partes de su aplicación se basarán en la implementación y qué partes se aplazarán (y cómo se aplazarán).
- Crítico
No se necesita ninguna acción. Todo lo que implemente se basará en push . - Diferido
Una parte específica de la aplicación no se creará en el momento de la implementación, se aplazará para que se cree bajo demanda cada vez que se produzca la primera solicitud, luego se almacenará en caché como cualquier otro recurso de su tipo.
Creación de un constructor bajo demanda
En primer lugar, agregue un paquete netlify/functions como devDependency
a su proyecto:
yarn add -D @netlify/functions
Una vez hecho esto, es lo mismo que crear una nueva función de Netlify. Si no ha establecido un directorio específico para ellos, diríjase a netlify/functions/
y cree un archivo con cualquier nombre para su constructor.
import type { Handler } from '@netlify/functions' import { builder } from '@netlify/functions' const myHandler: Handler = async (event, context) => { return { statusCode: 200, body: JSON.stringify({ message: 'Built on-demand! ' }), } } export const handler = builder(myHandler)
Como puede ver en el fragmento anterior, el generador bajo demanda se separa de una función Netlify regular porque envuelve su controlador dentro de un método builder()
. Este método conecta nuestra función con las tareas de compilación. Y eso es todo lo que necesita para aplazar una parte de su solicitud para la construcción solo cuando sea necesario. ¡Pequeñas construcciones incrementales desde el primer momento!
Siguiente.js en Netlify
Para crear una aplicación Next.js en Netlify, hay 2 complementos importantes que se deben agregar para tener una mejor experiencia en general: Netlify Plugin Cache Next.js y Essential Next-on-Netlify. El primero almacena en caché su NextJS de manera más eficiente y debe agregarlo usted mismo, mientras que el segundo realiza algunos ajustes leves en la forma en que se construye la arquitectura de Next.js para que se ajuste mejor a la de Netlify y esté disponible de manera predeterminada para cada nuevo proyecto que Netlify puede identificar. utilizando Next.js.
Constructores bajo demanda con Next.js
Rendimiento de creación, rendimiento de implementación, almacenamiento en caché, experiencia de desarrollador. Todos estos son temas muy importantes, pero son muchos, y lleva tiempo configurarlos correctamente. Luego llegamos a esa vieja discusión sobre centrarse en la experiencia del desarrollador en lugar de la experiencia del usuario. Que es el momento en que las cosas van a un lugar oculto en una acumulación para ser olvidadas. Realmente no.
Netlify te respalda. En solo unos pocos pasos, podemos aprovechar toda la potencia de Jamstack en nuestra aplicación Next.js. Es hora de arremangarse y armarlo todo ahora.
Definición de rutas renderizadas previamente
Si ha trabajado antes con generación estática dentro de Next.js, probablemente haya oído hablar del método getStaticPaths
. Este método está diseñado para rutas dinámicas (plantillas de página que representarán una amplia gama de páginas). Sin detenernos demasiado en las complejidades de este método, es importante tener en cuenta que el tipo de devolución es un objeto con 2 claves, como en nuestra Prueba de concepto, este será el archivo de ruta dinámica [Pokemon]:
export async function getStaticPaths() { return { paths: [], fallback: 'blocking', } }
-
paths
es unaarray
que lleva a cabo todas las rutas que coinciden con esta ruta que se renderizará previamente -
fallback
tiene 3 valores posibles: bloqueo,true
ofalse
En nuestro caso, nuestro getStaticPaths
está determinando:
- No se renderizarán previamente rutas;
- Cada vez que se llame a esta ruta, no proporcionaremos una plantilla alternativa, representaremos la página a pedido y mantendremos al usuario esperando, bloqueando la aplicación para que no haga nada más.
Cuando use On-Demand Builders, asegúrese de que su estrategia de respaldo cumpla con los objetivos de su aplicación, los documentos oficiales de Next.js: los documentos de respaldo son muy útiles.
Antes de On-Demand Builders, nuestro getStaticPaths
era ligeramente diferente:
export async function getStaticPaths() { const { pokemons } = await fetchPkmList() return { paths: pokemons.map(({ name }) => ({ params: { pokemon: name } })), fallback: false, } }
Estábamos recopilando una lista de todas las páginas de pokemon que pretendíamos tener, asignamos todos los objetos de pokemon
a solo una string
con el nombre de pokemon y reenviamos el objeto { params }
que lo transportaba a getStaticProps
. Nuestro fallback
se estableció en false
porque si una ruta no coincidía, queríamos que Next.js lanzara una página 404: Not Found
.
Puede verificar ambas versiones implementadas en Netlify:
- Con On-Demand Builder: código, en vivo
- Generado completamente estático: código, en vivo
El código también es de código abierto en Github y puede implementarlo usted mismo fácilmente para verificar los tiempos de compilación. Y con esta cola, pasamos a nuestro próximo tema.
Tiempos de construcción
Como se mencionó anteriormente, la demostración anterior es en realidad una prueba de concepto , nada es realmente bueno o malo si no podemos medirlo. Para nuestro pequeño estudio, fui a PokeAPI y decidí atrapar a todos los Pokémon.
Para fines de reproducibilidad, limité nuestra solicitud (a 1000
). Estos no están realmente todos dentro de la API, pero exige que la cantidad de páginas sea la misma para todas las compilaciones, independientemente de si las cosas se actualizan en algún momento.
export const fetchPkmList = async () => { const resp = await fetch(`${API}pokemon?limit=${LIMIT}`) const { count, results, }: { count: number results: { name: string url: string }[] } = await resp.json() return { count, pokemons: results, limit: LIMIT, } }
Y luego disparó ambas versiones en ramas separadas a Netlify, gracias a las implementaciones de vista previa, pueden coexistir básicamente en el mismo entorno. Para evaluar realmente la diferencia entre ambos métodos, el enfoque ODB fue extremo, no se renderizaron páginas previamente para esa ruta dinámica. Aunque no se recomienda para escenarios del mundo real (querrá renderizar previamente sus rutas con mucho tráfico), marca claramente el rango de mejora del rendimiento en tiempo de compilación que podemos lograr con este enfoque.
Estrategia | Número de páginas | Número de activos | Tiempo de construcción | Tiempo total de implementación |
---|---|---|---|---|
Generado completamente estático | 1002 | 1005 | 2 minutos 32 segundos | 4 minutos 15 segundos |
Constructores bajo demanda | 2 | 0 | 52 segundos | 52 segundos |
Las páginas de nuestra pequeña aplicación PokeDex son bastante pequeñas, los activos de imagen son muy reducidos, pero las ganancias en el tiempo de implementación son muy significativas. Si una aplicación tiene una cantidad media o grande de rutas, definitivamente vale la pena considerar la estrategia ODB.
Hace que sus implementaciones sean más rápidas y, por lo tanto, más confiables. El impacto en el rendimiento solo ocurre en la primera solicitud, a partir de la solicitud posterior y en adelante, la página procesada se almacenará en caché directamente en Edge, lo que hace que el rendimiento sea exactamente el mismo que el generado completamente estático.
El futuro: renderizado persistente distribuido
El mismo día, se anunciaron los On-Demand Builders y se pusieron en acceso anticipado, Netlify también publicó su Solicitud de comentarios sobre el renderizado persistente distribuido (DPR).
DPR es el siguiente paso para On-Demand Builders. Aprovecha las compilaciones más rápidas al hacer uso de pasos de compilación asincrónicos y luego almacenar en caché los activos hasta que realmente se actualicen. No más compilaciones completas para el sitio web de una página de 10k. DPR otorga a los desarrolladores un control total sobre los sistemas de compilación e implementación a través del almacenamiento en caché sólido y el uso de On-Demand Builders.
Imagínese este escenario: un sitio web de comercio electrónico tiene 10k páginas de productos, esto significa que tomaría alrededor de 2 horas construir la aplicación completa para su implementación. No necesitamos discutir lo doloroso que es esto.
Con DPR, podemos configurar las 500 páginas principales para construir en cada implementación. Nuestras páginas de mayor tráfico siempre están listas para nuestros usuarios. Pero, somos una tienda, es decir, cada segundo cuenta. Entonces, para las otras 9500 páginas, podemos configurar un enlace posterior a la compilación para activar sus constructores, implementando el resto de nuestras páginas de forma asíncrona y almacenando en caché de inmediato. Ningún usuario resultó herido, nuestro sitio web se actualizó con la compilación más rápida posible y todo lo demás que no existía en la memoria caché se almacenó.
Conclusión
Aunque muchos de los puntos de discusión en este artículo fueron conceptuales y la implementación está por definir, estoy entusiasmado con el futuro de Jamstack. Los avances que estamos haciendo como comunidad giran en torno a la experiencia del usuario final.
¿Cuál es su opinión sobre el renderizado persistente distribuido? ¿Ha probado On-Demand Builders en su aplicación? Déjame saber más en los comentarios o llámame en Twitter. ¡Tengo mucha curiosidad!
Referencias
- "Una guía completa para la regeneración estática incremental (ISR) con Next.js", Lee Robinson
- "Compilaciones más rápidas para sitios grandes en Netlify con creadores bajo demanda", Asavari Tayal, blog de Netlify
- "Representación persistente distribuida: un nuevo enfoque Jamstack para compilaciones más rápidas", Matt Biilmann, Netlify Blog
- "Representación persistente distribuida (DPR)", Cassidy Williams, GitHub