Décomposer les constructions volumineuses avec Netlify et Next.js

Publié: 2022-03-10
Résumé rapide ↬ La génération statique est idéale pour les performances - jusqu'à ce que l'application devienne trop volumineuse et que les temps de construction explosent. Aujourd'hui, nous verrons comment les nouveaux constructeurs à la demande de Netlify peuvent résoudre ce problème. De plus, nous l'associons à la régénération statique incrémentielle de Next.js pour la meilleure expérience utilisateur et développeur. Et, bien sûr, comparez ces résultats !

L'une des plus grandes difficultés de travailler avec des sites Web générés de manière statique est la lenteur progressive des versions à mesure que votre application se développe. Il s'agit d'un problème inévitable auquel toute pile est confrontée à un moment donné et qui peut survenir à différents endroits selon le type de produit avec lequel vous travaillez.

Par exemple, si votre application comporte plusieurs pages (vues, routes) lors de la génération de l'artefact de déploiement, chacune de ces routes devient un fichier. Ensuite, une fois que vous avez atteint des milliers, vous commencez à vous demander quand vous pourrez vous déployer sans avoir à planifier à l'avance. Ce scénario est courant sur les plateformes de commerce électronique ou les blogs, qui représentent déjà une grande partie du Web, mais pas la totalité. Les routes ne sont cependant pas le seul goulot d'étranglement possible.

Une application gourmande en ressources finira également par atteindre ce tournant. De nombreux générateurs statiques effectuent une optimisation des actifs pour garantir la meilleure expérience utilisateur. Sans optimisations de construction (constructions incrémentielles, mise en cache, nous y reviendrons bientôt), cela finira par devenir également ingérable - pensez à parcourir toutes les images d'un site Web : redimensionner, supprimer et/ou créer de nouveaux fichiers encore et encore. Et une fois que tout cela est fait : rappelez-vous que Jamstack sert nos applications depuis les bords du réseau de diffusion de contenu . Nous devons donc toujours déplacer les éléments du serveur sur lequel ils ont été compilés vers les bords du réseau.

Architecture des services généraux de Jamstack
Architecture de service général Jamstack ( Grand aperçu )

En plus de tout cela, il y a aussi un autre fait : les données sont souvent dynamiques, ce qui signifie que lorsque nous construisons notre application et la déployons, cela peut prendre quelques secondes, quelques minutes, voire une heure. Pendant ce temps, le monde continue de tourner, et si nous récupérons des données ailleurs, notre application est vouée à devenir obsolète. Inacceptable! Reconstruisez pour mettre à jour !

Construire une fois, mettre à jour si nécessaire

La résolution des constructions volumineuses est une priorité pour pratiquement toutes les plates-formes, frameworks ou services Jamstack depuis un certain temps. De nombreuses solutions tournent autour des builds incrémentiels. En pratique, cela signifie que les builds seront aussi volumineux que les différences qu'ils portent par rapport au déploiement actuel.

Définir un algorithme de diff n'est cependant pas une tâche facile. Pour que l' utilisateur final bénéficie réellement de cette amélioration, des stratégies d'invalidation du cache doivent être prises en compte. Pour faire court : nous ne voulons pas invalider le cache d'une page ou d'un élément qui n'a pas changé.

Next.js a proposé la régénération statique incrémentielle ( ISR ). Essentiellement, c'est un moyen de déclarer pour chaque route à quelle fréquence nous voulons qu'elle se reconstruise. Sous le capot, cela simplifie une grande partie du travail côté serveur. Parce que chaque route (dynamique ou non) se reconstruira dans un délai spécifique, et elle s'intègre parfaitement dans l'axiome Jamstack d'invalidation du cache à chaque build. Considérez-le comme l'en-tête max-age mais pour les itinéraires dans votre application Next.js.

Pour démarrer votre application, ISR n'est qu'à une propriété de configuration. Sur votre composant de route (dans le répertoire /pages ), accédez à votre méthode getStaticProps et ajoutez la clé de revalidate à l'objet de retour :

 export async function getStaticProps() { const { limit, count, pokemons } = await fetchPokemonList() return { props: { limit, count, pokemons, }, revalidate: 3600 // seconds } }

L'extrait ci-dessus garantira que ma page se reconstruira toutes les heures et récupèrera plus de Pokémon à afficher.

Nous obtenons toujours les builds en bloc de temps en temps (lors de la publication d'un nouveau déploiement). Mais cela nous permet de dissocier le contenu du code, en déplaçant le contenu vers un système de gestion de contenu (CMS), nous pouvons mettre à jour les informations en quelques secondes, quelle que soit la taille de notre application. Adieu les webhooks pour la mise à jour des fautes de frappe !

Constructeurs à la demande

Netlify a récemment lancé On-Demand Builders, qui est leur approche pour prendre en charge ISR pour Next.js, mais fonctionne également sur des frameworks tels qu'Eleventy et Nuxt. Lors de la session précédente, nous avons établi que l'ISR était un grand pas vers des temps de construction plus courts et a traité une partie importante des cas d'utilisation. Néanmoins, les mises en garde étaient là :

  1. Complète s'appuie sur un déploiement continu.
    L'étape incrémentielle ne se produit qu'après le déploiement et pour les données. Il n'est pas possible d'expédier le code de manière incrémentielle
  2. Les builds incrémentiels sont un produit du temps.
    Le cache est invalidé sur une base temporelle. Ainsi, des builds inutiles peuvent se produire ou les mises à jour nécessaires peuvent prendre plus de temps en fonction de la période de revalidation définie dans le code.

La nouvelle infrastructure de déploiement de Netlify permet aux développeurs de créer une logique pour déterminer quelles parties de leur application s'appuieront sur le déploiement et quelles parties seront différées (et comment elles seront différées).

  • Critique
    Aucune action n'est nécessaire. Tout ce que vous déployez sera construit sur push .
  • Différé
    Une partie spécifique de l'application ne sera pas construite lors du déploiement, elle sera différée pour être construite à la demande chaque fois que la première demande se produit, puis elle sera mise en cache comme toute autre ressource de son type.

Création d'un générateur à la demande

Tout d'abord, ajoutez un package netlify/functions en tant que devDependency à votre projet :

 yarn add -D @netlify/functions

Une fois cela fait, cela revient à créer une nouvelle fonction Netlify. Si vous n'avez pas défini de répertoire spécifique pour eux, rendez-vous sur netlify/functions/ et créez un fichier de n'importe quel nom pour votre constructeur.

 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)

Comme vous pouvez le voir dans l'extrait ci-dessus, le générateur à la demande se sépare d'une fonction Netlify normale car il encapsule son gestionnaire dans une méthode builder() . Cette méthode connecte notre fonction aux tâches de construction. Et c'est tout ce dont vous avez besoin pour qu'une partie de votre application soit différée pour la construction uniquement lorsque cela est nécessaire. Petites constructions incrémentielles dès le départ !

Next.js sur Netlify

Pour construire une application Next.js sur Netlify, il y a 2 plugins importants qu'il faut ajouter pour avoir une meilleure expérience en général : Netlify Plugin Cache Next.js et Essential Next-on-Netlify. Le premier met en cache votre NextJS plus efficacement et vous devez l'ajouter vous-même, tandis que le second apporte quelques légers ajustements à la façon dont l'architecture Next.js est construite afin qu'elle s'adapte mieux à Netlify et soit disponible par défaut pour chaque nouveau projet que Netlify peut identifier est en utilisant Next.js.

Générateurs à la demande avec Next.js

Renforcement des performances, déploiement des performances, mise en cache, expérience développeur. Ce sont tous des sujets très importants, mais c'est beaucoup - et prend du temps à mettre en place correctement. Ensuite, nous arrivons à cette vieille discussion sur le fait de se concentrer sur l'expérience du développeur plutôt que sur l'expérience de l'utilisateur. C'est le moment où les choses vont dans un endroit caché d'un arriéré pour être oubliées. Pas vraiment.

Netlify a votre dos. En quelques étapes seulement, nous pouvons exploiter toute la puissance de Jamstack dans notre application Next.js. Il est temps de retrousser nos manches et de tout mettre ensemble maintenant.

Définition de chemins pré-rendus

Si vous avez déjà travaillé avec la génération statique dans Next.js, vous avez probablement entendu parler de la méthode getStaticPaths . Cette méthode est destinée aux routes dynamiques (modèles de page qui rendront un large éventail de pages). Sans trop s'attarder sur les subtilités de cette méthode, il est important de noter que le type de retour est un objet avec 2 clés, comme dans notre Proof-of-Concept ce sera [Pokemon]fichier de route dynamique :

 export async function getStaticPaths() { return { paths: [], fallback: 'blocking', } }
  • paths est un array réalisant tous les chemins correspondant à cette route qui sera pré-rendu
  • fallback a 3 valeurs possibles : blocking, true ou false

Dans notre cas, notre getStaticPaths détermine :

  1. Aucun chemin ne sera pré-rendu ;
  2. Chaque fois que cette route est appelée, nous ne servirons pas de modèle de secours, nous rendrons la page à la demande et ferons attendre l'utilisateur, empêchant l'application de faire quoi que ce soit d'autre.

Lorsque vous utilisez des constructeurs à la demande, assurez-vous que votre stratégie de secours répond aux objectifs de votre application, les documents officiels Next.js : les documents de secours sont très utiles.

Avant les constructeurs à la demande, notre getStaticPaths était légèrement différent :

 export async function getStaticPaths() { const { pokemons } = await fetchPkmList() return { paths: pokemons.map(({ name }) => ({ params: { pokemon: name } })), fallback: false, } }

Nous rassemblions une liste de toutes les pages pokemon que nous avions l'intention d'avoir, mappions tous les objets pokemon sur une string avec le nom du pokemon et renvoyions l'objet { params } le portant à getStaticProps . Notre solution de fallback était définie sur false car si une route ne correspondait pas, nous voulions que Next.js lance une page 404: Not Found .

Vous pouvez vérifier les deux versions déployées sur Netlify :

  • Avec On-Demand Builder : codez, vivez
  • Génération entièrement statique : code, live

Le code est également open source sur Github et vous pouvez facilement le déployer vous-même pour vérifier les temps de construction. Et avec cette file d'attente, nous passons au sujet suivant.

Temps de construction

Comme mentionné ci-dessus, la démo précédente est en fait une preuve de concept , rien n'est vraiment bon ou mauvais si nous ne pouvons pas mesurer. Pour notre petite étude, je suis allé sur la PokeAPI et j'ai décidé d'attraper tous les pokémons.

Dans un souci de reproductibilité, j'ai plafonné notre demande (à 1000 ). Ce ne sont pas vraiment tous dans l'API, mais cela impose que le nombre de pages soit le même pour toutes les versions, que les choses soient mises à jour à tout moment.

 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, } }

Et puis a lancé les deux versions dans des branches séparées vers Netlify, grâce aux déploiements en avant-première, elles peuvent coexister essentiellement dans le même environnement. Pour vraiment évaluer la différence entre les deux méthodes, l'approche ODB était extrême, aucune page n'était pré-rendue pour cette route dynamique. Bien que non recommandé pour les scénarios du monde réel (vous souhaiterez pré-rendre vos itinéraires à fort trafic), cela marque clairement la gamme d'amélioration des performances au moment de la construction que nous pouvons obtenir avec cette approche.

Stratégie Nombre de pages Nombre d'actifs Temps de construction Temps de déploiement total
Entièrement statique généré 1002 1005 2 minutes 32 secondes 4 minutes 15 secondes
Constructeurs à la demande 2 0 52 secondes 52 secondes

Les pages de notre petite application PokeDex sont assez petites, les actifs d'image sont très maigres, mais les gains sur le temps de déploiement sont très importants. Si une application a un nombre moyen à élevé de routes, cela vaut vraiment la peine d'envisager la stratégie ODB.

Cela rend vos déploiements plus rapides et donc plus fiables. L'atteinte des performances ne se produit qu'à la toute première requête, à partir de la requête suivante et au-delà, la page rendue sera mise en cache directement sur Edge, ce qui rendra les performances exactement identiques à celles de la génération entièrement statique.

L'avenir : le rendu persistant distribué

Le même jour, les constructeurs à la demande ont été annoncés et mis en accès anticipé, Netlify a également publié son appel à commentaires sur le rendu persistant distribué (DPR).

Le DPR est la prochaine étape pour les constructeurs à la demande. Il capitalise sur des builds plus rapides en utilisant ces étapes de construction asynchrones, puis en mettant en cache les actifs jusqu'à ce qu'ils soient réellement mis à jour. Plus de versions complètes pour le site Web d'une page de 10 000 pages. DPR permet aux développeurs d'avoir un contrôle total sur les systèmes de construction et de déploiement grâce à une mise en cache solide et à l'utilisation de constructeurs à la demande.

Imaginez ce scénario : un site Web de commerce électronique contient 10 000 pages de produits, ce qui signifie qu'il faudrait environ 2 heures pour créer l'intégralité de l'application à déployer. Nous n'avons pas besoin de discuter à quel point c'est douloureux.

Avec DPR, nous pouvons définir les 500 premières pages à développer à chaque déploiement. Nos pages les plus fréquentées sont toujours prêtes pour nos utilisateurs. Mais nous sommes une boutique, c'est-à-dire que chaque seconde compte. Ainsi, pour les 9 500 autres pages, nous pouvons définir un crochet post-construction pour déclencher leurs générateurs, en déployant le reste de nos pages de manière asynchrone et en les mettant immédiatement en cache. Aucun utilisateur n'a été blessé, notre site Web a été mis à jour avec la version la plus rapide possible, et tout ce qui n'existait pas dans le cache a ensuite été stocké.

Conclusion

Bien que de nombreux points de discussion dans cet article soient conceptuels et que la mise en œuvre reste à définir, je suis enthousiasmé par l'avenir de Jamstack. Les avancées que nous réalisons en tant que communauté tournent autour de l'expérience de l'utilisateur final.

Quelle est votre opinion sur le rendu persistant distribué ? Avez-vous essayé les constructeurs à la demande dans votre application ? Faites-moi savoir plus dans les commentaires ou appelez-moi sur Twitter. je suis vraiment curieuse !

Les références

  • "Un guide complet de la régénération statique incrémentielle (ISR) avec Next.js", Lee Robinson
  • "Constructions plus rapides pour les grands sites sur Netlify avec les constructeurs à la demande", Asavari Tayal, blog Netlify
  • "Rendu persistant distribué : une nouvelle approche Jamstack pour des constructions plus rapides", Matt Biilmann, blog Netlify
  • "Rendu persistant distribué (DPR)", Cassidy Williams, GitHub