Reconstruction d'un grand site Web de commerce électronique avec Next.js (étude de cas)

Publié: 2022-03-10
Résumé rapide ↬ Nous sommes passés d'une plateforme de commerce électronique intégrée plus traditionnelle à une plateforme sans tête avec Next.js. Voici les leçons les plus importantes apprises lors de la reconstruction d'un grand site de commerce électronique avec Next.js.

Dans notre entreprise, Unplatform, nous construisons des sites de commerce électronique depuis des décennies. Au cours de ces années, nous avons vu la pile technologique évoluer des pages rendues par le serveur avec quelques JavaScript et CSS mineurs vers des applications JavaScript complètes.

La plate-forme que nous avons utilisée pour nos sites de commerce électronique était basée sur ASP.NET et lorsque les visiteurs ont commencé à s'attendre à plus d'interaction, nous avons ajouté React pour le front-end. Bien que mélanger les concepts d'un framework Web serveur comme ASP.NET avec un framework Web côté client comme React ait rendu les choses plus compliquées, nous étions assez satisfaits de la solution. C'était jusqu'à ce que nous passions en production avec notre client le plus fréquenté. Dès le moment où nous avons commencé à vivre, nous avons rencontré des problèmes de performances . Les Core Web Vitals sont importants, encore plus dans le commerce électronique. Dans cette étude Deloitte : Milliseconds Make Millions, les enquêteurs ont analysé les données des sites mobiles de 37 marques différentes. En conséquence, ils ont constaté qu'une amélioration des performances de 0,1 s peut entraîner une augmentation de 10 % de la conversion.

Pour atténuer les problèmes de performances, nous avons dû ajouter de nombreux serveurs supplémentaires (non budgétés) et avons dû mettre en cache de manière agressive les pages sur un proxy inverse. Cela nous a même obligés à désactiver certaines fonctionnalités du site. Nous avons fini par avoir une solution très compliquée et coûteuse qui, dans certains cas, ne servait que statiquement certaines pages.

De toute évidence, cela ne semblait pas correct, jusqu'à ce que nous découvrions Next.js . Next.js est un framework Web basé sur React qui vous permet de générer des pages de manière statique, mais vous pouvez également toujours utiliser le rendu côté serveur, ce qui le rend idéal pour le commerce électronique. Il peut être hébergé sur un CDN comme Vercel ou Netlify, ce qui se traduit par une latence plus faible . Vercel et Netlify utilisent également des fonctions sans serveur pour le rendu côté serveur, qui est le moyen le plus efficace d'évoluer.

Défis

Développer avec Next.js est incroyable, mais il y a certainement des défis. L'expérience de développeur avec Next.js est quelque chose que vous devez simplement expérimenter. Le code que vous écrivez se visualise instantanément dans votre navigateur et la productivité monte en flèche. C'est aussi un risque car vous pouvez facilement vous concentrer trop sur la productivité et négliger la maintenabilité de votre code. Au fil du temps, cela et la nature non typée de JavaScript peuvent entraîner la dégradation de votre base de code. Le nombre de bugs augmente et la productivité commence à baisser.

Cela peut également être difficile du côté de l'exécution . Les plus petits changements dans votre code peuvent entraîner une baisse des performances et d'autres Core Web Vitals. De plus, une utilisation négligente du rendu côté serveur peut entraîner des coûts de service inattendus.

Examinons de plus près les leçons que nous avons apprises pour surmonter ces défis.

  1. Modularisez votre base de code
  2. Lint et formatez votre code
  3. Utiliser TypeScript
  4. Planifier la performance et mesurer la performance
  5. Ajoutez des contrôles de performance à votre Quality Gate
  6. Ajouter des tests automatisés
  7. Gérez agressivement vos dépendances
  8. Utiliser un service d'agrégation de journaux
  9. La fonctionnalité de réécriture de Next.js permet une adoption incrémentielle
Plus après saut! Continuez à lire ci-dessous ↓

Leçon apprise : modularisez votre base de code

Les frameworks frontaux comme Next.js facilitent le démarrage de nos jours. Vous venez d'exécuter npx create-next-app et vous pouvez commencer à coder. Mais si vous ne faites pas attention et que vous commencez à taper du code sans penser à la conception, vous pourriez vous retrouver avec une grosse boule de boue.

Lorsque vous exécutez npx create-next-app , vous aurez une structure de dossiers comme celle-ci (c'est également ainsi que la plupart des exemples sont structurés) :

 /public logo.gif /src /lib /hooks useForm.js /api content.js /components Header.js Layout.js /pages Index.js

Nous avons commencé avec la même structure. Nous avions quelques sous-dossiers dans le dossier des composants pour les composants plus volumineux, mais la plupart des composants se trouvaient dans le dossier des composants racine. Il n'y a rien de mal avec cette approche et c'est bien pour les petits projets. Cependant, au fur et à mesure que notre projet grandissait, il est devenu plus difficile de raisonner sur les composants et où ils sont utilisés. Nous avons même trouvé des composants qui n'étaient plus du tout utilisés ! Cela favorise également une grosse boule de boue, car il n'y a pas d'indications claires sur quel code devrait dépendre de quel autre code.

Pour résoudre ce problème, nous avons décidé de refactoriser la base de code et de regrouper le code par modules fonctionnels (un peu comme les modules NPM) au lieu de concepts techniques :

 /src /modules /catalog /components productblock.js /checkout /api cartservice.js /components cart.js

Dans ce petit exemple, il y a un module de paiement et un module de catalogue. Regrouper le code de cette manière conduit à une meilleure découverte : en regardant simplement la structure des dossiers, vous savez exactement quel type de fonctionnalité se trouve dans la base de code et où la trouver. Cela rend également beaucoup plus facile de raisonner sur les dépendances . Dans la situation précédente, il y avait beaucoup de dépendances entre les composants. Nous avons eu des demandes d'extraction de modifications dans le processus de paiement qui ont également eu un impact sur les composants du catalogue. Cela a augmenté le nombre de conflits de fusion et a rendu plus difficile les modifications.

La solution qui fonctionnait le mieux pour nous était de maintenir les dépendances entre les modules à un minimum absolu (si vous avez vraiment besoin d'une dépendance, assurez-vous qu'elle est unidirectionnelle) et d'introduire un niveau « projet » qui relie tout :

 /src /modules /common /atoms /lib /catalog /components productblock.js /checkout /api cartservice.js /components cart.js /search /project /layout /components /templates productdetail.js cart.js /pages cart.js

Un aperçu visuel de cette solution :

Un aperçu d'un exemple de projet modularisé
Un aperçu d'un exemple de projet modularisé ( Grand aperçu )

Le niveau projet contient le code de mise en page du site e-commerce et des modèles de page. Dans Next.js, un composant de page est une convention et aboutit à une page physique. D'après notre expérience, ces pages doivent souvent réutiliser la même implémentation et c'est pourquoi nous avons introduit le concept de « modèles de pages ». Les modèles de page utilisent les composants des différents modules, par exemple, le modèle de page de détail du produit utilisera des composants du catalogue pour afficher les informations sur le produit, mais également un composant d'ajout au panier du module de paiement.

Nous avons également un module commun, car il reste encore du code qui doit être réutilisé par les modules fonctionnels. Il contient des atomes simples qui sont des composants React utilisés pour fournir une apparence et une sensation cohérentes. Il contient également du code d'infrastructure, pensez à certains crochets de réaction génériques ou au code client GraphQL.

Avertissement : Assurez-vous que le code dans le module commun est stable et réfléchissez toujours à deux fois avant d'ajouter du code ici, afin d'éviter tout code emmêlé.

Micro-frontaux

Dans des solutions encore plus grandes ou lorsque vous travaillez avec différentes équipes, il peut être judicieux de diviser encore plus l'application en ce que l'on appelle des micro-interfaces. En bref, cela signifie diviser encore plus l'application en plusieurs applications physiques hébergées indépendamment sur différentes URL. Par exemple : checkout.mydomain.com et catalog.mydomain.com. Ceux-ci sont ensuite intégrés par une application différente qui agit comme un proxy.

La fonctionnalité de réécriture de Next.js est idéale pour cela et son utilisation comme celle-ci est prise en charge par ce qu'on appelle les zones multiples.

Un exemple de configuration multizone
Un exemple de configuration multi-zone ( Grand aperçu )

L'avantage du multi-zones est que chaque zone gère ses propres dépendances. Cela facilite également l'évolution progressive de la base de code : si une nouvelle version de Next.js ou de React sort, vous pouvez mettre à niveau les zones une par une au lieu d'avoir à mettre à jour l'intégralité de la base de code en une seule fois. Dans une organisation multi-équipes, cela peut réduire considérablement les dépendances entre les équipes.

Lectures complémentaires

  • "Structure du projet Next.js", Yannick Wittwer, Medium
  • "Un guide 2021 sur la structuration de votre projet Next.js de manière flexible et efficace", Vadorequest, Dev.to.
  • "Micro-interfaces", Michael Geers

Leçon apprise : charpie et formatage de votre code

C'est quelque chose que nous avons appris dans un projet précédent : si vous travaillez dans la même base de code avec plusieurs personnes et que vous n'utilisez pas de formateur, votre code deviendra rapidement très incohérent. Même si vous utilisez des conventions de codage et faites des révisions, vous commencerez bientôt à remarquer les différents styles de codage, donnant une impression désordonnée du code.

Un linter vérifiera votre code pour les problèmes potentiels et un formateur s'assurera que le code est formaté de manière cohérente. Nous utilisons ESLint & plus joli et pensons qu'ils sont géniaux. Vous n'avez pas à penser au style de codage, ce qui réduit la charge cognitive pendant le développement.

Heureusement, Next.js 11 prend désormais en charge ESLint prêt à l'emploi (https://nextjs.org/blog/next-11), ce qui le rend très facile à configurer en exécutant npx next lint. Cela vous fait gagner beaucoup de temps car il est livré avec une configuration par défaut pour Next.js. Par exemple, il est déjà configuré avec une extension ESLint pour React. Mieux encore, il est livré avec une nouvelle extension spécifique à Next.js qui détectera même les problèmes avec votre code qui pourraient potentiellement avoir un impact sur les Core Web Vitals de votre application ! Dans un paragraphe ultérieur, nous parlerons des portails de qualité qui peuvent vous aider à empêcher de pousser du code vers un produit qui blesse accidentellement vos Core Web Vitals. Cette extension vous donne des commentaires beaucoup plus rapidement, ce qui en fait un excellent ajout.

Lectures complémentaires

  • "ESLint", Next.js Docs
  • "ESLint", site officiel

Leçon apprise : utiliser TypeScript

Au fur et à mesure que les composants étaient modifiés et refactorisés, nous avons remarqué que certains des accessoires des composants n'étaient plus utilisés. De plus, dans certains cas, nous avons rencontré des bogues en raison de types d'accessoires manquants ou incorrects transmis aux composants.

TypeScript est un sur-ensemble de JavaScript et ajoute des types, ce qui permet à un compilateur de vérifier statiquement votre code, un peu comme un linter sur les stéroïdes.

Au début du projet, nous ne voyions pas vraiment l'intérêt d'ajouter TypeScript. Nous avons pensé que c'était juste une abstraction inutile. Cependant, un de nos collègues a eu de bonnes expériences avec TypeScript et nous a convaincus de l'essayer. Heureusement, Next.js dispose d'un excellent support TypeScript prêt à l'emploi et TypeScript vous permet de l'ajouter progressivement à votre solution. Cela signifie que vous n'avez pas besoin de réécrire ou de convertir l'intégralité de votre base de code en une seule fois, mais vous pouvez commencer à l'utiliser immédiatement et convertir lentement le reste de la base de code.

Une fois que nous avons commencé à migrer les composants vers TypeScript, nous avons immédiatement trouvé des problèmes avec des valeurs erronées transmises aux composants et aux fonctions. De plus, la boucle de rétroaction du développeur est devenue plus courte et vous êtes informé des problèmes avant d'exécuter l'application dans le navigateur. Un autre grand avantage que nous avons trouvé est qu'il est beaucoup plus facile de refactoriser le code : il est plus facile de voir où le code est utilisé et vous repérez immédiatement les accessoires et le code des composants inutilisés. En bref, les avantages de TypeScript :

  1. Réduit le nombre de bugs
  2. Facilite la refactorisation de votre code
  3. Le code devient plus facile à lire

Lectures complémentaires

  • « TypeScript », documentation Next.js
  • TypeScript, site officiel

Leçon apprise : planifier la performance et mesurer la performance

Next.js prend en charge différents types de pré-rendu : génération statique et rendu côté serveur. Pour de meilleures performances, il est recommandé d'utiliser la génération statique, qui se produit pendant la construction, mais ce n'est pas toujours possible. Pensez aux pages de détails des produits qui contiennent des informations sur les stocks. Ce type d'informations change souvent et l'exécution d'une version à chaque fois ne s'adapte pas bien. Heureusement, Next.js prend également en charge un mode appelé Incremental Static Regeneration (ISR), qui génère toujours statiquement la page, mais en génère une nouvelle en arrière-plan toutes les x secondes. Nous avons appris que ce modèle fonctionne très bien pour les applications plus importantes. Les performances sont toujours excellentes, cela nécessite moins de temps CPU que le rendu côté serveur et cela réduit les temps de construction : les pages ne sont générées qu'à la première requête. Pour chaque page que vous ajoutez, vous devez penser au type de rendu nécessaire. Tout d'abord, voyez si vous pouvez utiliser la génération statique ; sinon, optez pour la régénération statique incrémentielle, et si cela n'est pas possible non plus, vous pouvez toujours utiliser le rendu côté serveur.

Next.js détermine automatiquement le type de rendu en fonction de l'absence des méthodes getServerSideProps et getInitialProps sur la page. Il est facile de faire une erreur, ce qui pourrait entraîner l'affichage de la page sur le serveur au lieu d'être générée de manière statique. La sortie d'une construction Next.js montre exactement quelle page utilise quel type de rendu, alors assurez-vous de vérifier cela. Cela aide également à surveiller la production et à suivre les performances des pages et le temps CPU impliqué. La plupart des hébergeurs vous facturent en fonction du temps CPU, ce qui évite les mauvaises surprises. Je décrirai comment nous surveillons cela dans le paragraphe Leçon apprise : Utiliser un service d'agrégation de journaux.

Taille du paquet

Pour avoir de bonnes performances, il est crucial de minimiser la taille du faisceau. Next.js a beaucoup de fonctionnalités prêtes à l'emploi qui aident, par exemple le fractionnement automatique du code. Cela garantira que seuls le JavaScript et le CSS requis sont chargés pour chaque page. Il génère également différents bundles pour le client et pour le serveur. Cependant, il est important de garder un œil sur ceux-ci. Par exemple, si vous importez des modules JavaScript dans le mauvais sens, le serveur JavaScript peut se retrouver dans le groupe client, ce qui augmente considérablement la taille du groupe client et nuit aux performances. L'ajout de dépendances NPM peut également avoir un impact important sur la taille du bundle.

Heureusement, Next.js est livré avec un analyseur de bundles qui vous donne un aperçu du code qui occupe quelle partie des bundles.

L'analyseur de bundles Webpack vous indique la taille des packages de votre bundle
L'analyseur de bundles Webpack vous montre la taille des packages de votre bundle ( Grand aperçu )

Lectures complémentaires

  • « Analyseur de bundle Next.js + Webpack », Vercel, GitHub
  • "Récupération de données", Next.js Docs

Leçon apprise : Ajoutez des contrôles de performance à votre portail de qualité

L'un des grands avantages de l'utilisation de Next.js est la possibilité de générer des pages de manière statique et de pouvoir déployer l'application à la périphérie (CDN), ce qui devrait se traduire par d'excellentes performances et Web Vitals. Nous avons appris que, même avec une technologie de pointe comme Next.js, il est vraiment difficile d'obtenir et de conserver un excellent score phare. Il est arrivé plusieurs fois qu'après avoir déployé quelques changements dans la production, le score phare ait chuté de manière significative. Pour reprendre le contrôle, nous avons ajouté des tests phares automatiques à notre portail qualité. Avec cette action Github, vous pouvez automatiquement ajouter des tests phares à vos demandes d'extraction. Nous utilisons Vercel et chaque fois qu'une demande d'extraction est créée, Vercel la déploie sur une URL d'aperçu et nous utilisons l'action Github pour exécuter des tests phares sur ce déploiement.

Un exemple des résultats phares sur une demande d'extraction Github
Un exemple des résultats phares sur une demande d'extraction Github ( Grand aperçu )

Si vous ne souhaitez pas configurer l'action GitHub vous-même, ou si vous souhaitez aller encore plus loin, vous pouvez également envisager un service de surveillance des performances tiers comme DebugBear. Vercel propose également une fonction Analytics, qui mesure les principaux Web Vitals de votre déploiement de production. Vercel Analytics collecte en fait les mesures des appareils de vos visiteurs, de sorte que ces scores correspondent vraiment à ce que vos visiteurs ressentent. Au moment de la rédaction, Vercel Analytics ne fonctionne que sur les déploiements de production.

Leçon apprise : ajouter des tests automatisés

Lorsque la base de code s'agrandit, il devient plus difficile de déterminer si vos modifications de code ont pu casser les fonctionnalités existantes. D'après notre expérience, il est essentiel d'avoir un bon ensemble de tests de bout en bout comme filet de sécurité. Même si vous avez un petit projet, cela peut vous faciliter la vie lorsque vous avez au moins quelques tests de fumée de base. Nous utilisons Cypress pour cela et nous l'aimons absolument. La combinaison de l'utilisation de Netlify ou de Vercel pour déployer automatiquement votre pull request sur un environnement temporaire et l'exécution de vos tests E2E n'a pas de prix.

Nous utilisons cypress-io/GitHub-action pour exécuter automatiquement les tests cypress par rapport à nos demandes d'extraction. Selon le type de logiciel que vous construisez, il peut être utile d'avoir également des tests plus granulaires utilisant Enzyme ou JEST. Le compromis est que ceux-ci sont plus étroitement couplés à votre code et nécessitent plus de maintenance.

Un exemple de contrôles automatisés sur une Github Pull Request
Un exemple de vérifications automatisées sur une demande d'extraction Github ( Grand aperçu )

Leçon apprise : Gérez agressivement vos dépendances

La gestion des dépendances devient une activité chronophage, mais tellement importante lors de la maintenance d'une grande base de code Next.js. NPM a rendu l'ajout de packages si facile et il semble y avoir un package pour tout de nos jours. Avec le recul, souvent, lorsque nous avons introduit un nouveau bogue ou subi une baisse des performances, cela avait quelque chose à voir avec un package NPM nouveau ou mis à jour.

Ainsi, avant d'installer un package, vous devez toujours vous poser les questions suivantes :

  • Quelle est la qualité du colis ?
  • Qu'est-ce que l'ajout de ce package signifiera pour la taille de mon bundle ?
  • Ce package est-il vraiment nécessaire ou existe-t-il des alternatives ?
  • Le paquet est-il toujours activement maintenu ?

Pour garder la taille du bundle petite et minimiser l'effort nécessaire pour maintenir ces dépendances, il est important de garder le nombre de dépendances aussi petit que possible. Votre futur moi vous en remerciera lors de la maintenance du logiciel.

Astuce : L'extension Import Cost VSCode affiche automatiquement la taille des packages importés.

Suivez les versions de Next.js

Suivre Next.js & React est important. Non seulement cela vous donnera accès à de nouvelles fonctionnalités, mais les nouvelles versions incluront également des corrections de bogues et des correctifs pour les problèmes de sécurité potentiels. Heureusement, Next.js rend la mise à niveau incroyablement facile en fournissant Codemods (https://nextjs.org/docs/advanced-features/codemods. Ce sont des transformations de code automatiques qui mettent automatiquement à jour votre code.

Mettre à jour les dépendances

Pour la même raison, il est important de conserver les versions Next.js et React actuelles ; il est également important de mettre à jour les autres dépendances. Le dependabot de Github (https://github.com/dependabot) peut vraiment aider ici. Il créera automatiquement des demandes d'extraction avec des dépendances mises à jour. Cependant, la mise à jour des dépendances peut potentiellement casser des choses, donc avoir des tests automatisés de bout en bout ici peut vraiment être une bouée de sauvetage.

Leçon apprise : utiliser un service d'agrégation de journaux

Pour s'assurer que l'application se comporte correctement et pour détecter les problèmes de manière préventive, nous avons constaté qu'il est absolument nécessaire de configurer un service d'agrégation de journaux. Vercel vous permet de vous connecter et d'afficher les journaux, mais ceux-ci sont diffusés en temps réel et ne sont pas conservés. Il ne prend pas non plus en charge la configuration des alertes et des notifications.

Certaines exceptions peuvent mettre longtemps à apparaître. Par exemple, nous avions configuré Stale-While-Revalidate pour une page particulière. À un moment donné, nous avons remarqué que les pages n'étaient pas actualisées et que d'anciennes données étaient servies. Après avoir vérifié la journalisation Vercel, nous avons constaté qu'une exception se produisait lors du rendu en arrière-plan de la page. En utilisant un service d'agrégation de journaux et en configurant une alerte pour les exceptions, nous aurions pu le détecter beaucoup plus tôt.

Les services d'agrégation de journaux peuvent également être utiles pour surveiller les limites des plans tarifaires de Vercel. La page d'utilisation de Vercel vous donne également un aperçu à ce sujet, mais l'utilisation d'un service d'agrégation de journaux vous permet d'ajouter des notifications lorsque vous atteignez un certain seuil. Mieux vaut prévenir que guérir, surtout en matière de facturation.

Vercel propose un certain nombre d'intégrations prêtes à l'emploi avec des services d'agrégation de journaux, notamment Datadog, Logtail, Logalert, Sentry, etc.

Affichage du journal des requêtes Next.js dans Datadog
Affichage du journal des requêtes Next.js dans Datadog ( Grand aperçu )

Lectures complémentaires

  • « Intégrations », Vercel

Leçon apprise : la fonctionnalité de réécriture de Next.js permet une adoption incrémentielle

À moins qu'il n'y ait de sérieux problèmes avec le site Web actuel, peu de clients seront ravis de réécrire l'intégralité du site Web. Mais que diriez-vous si vous pouviez commencer par reconstruire uniquement les pages qui comptent le plus en termes de Web Vitals ? C'est exactement ce que nous avons fait pour un autre client. Au lieu de reconstruire l'intégralité du site, nous ne reconstruisons que les pages les plus importantes pour le référencement et la conversion. Dans ce cas, les pages de détail et de catégorie du produit. En reconstruisant ceux avec Next.js, les performances ont considérablement augmenté.

La fonctionnalité de réécriture Next.js est idéale pour cela. Nous avons construit un nouveau front-end Next.js qui contient les pages du catalogue et l'avons déployé sur le CDN. Toutes les autres pages existantes sont réécrites par Next.js sur le site Web existant. De cette façon, vous pouvez commencer à bénéficier des avantages d'un site Next.js sans effort ni risque.

Lectures complémentaires

  • "Réécrit", Next.js Docs

Et après?

Lorsque nous avons publié la première version du projet et commencé à effectuer des tests de performance sérieux, nous avons été ravis des résultats. Non seulement les temps de réponse des pages et Web Vitals étaient bien meilleurs qu'auparavant, mais les coûts opérationnels représentaient également une fraction de ce qu'ils étaient auparavant. Next.js et JAMStack vous permettent généralement d'évoluer de la manière la plus rentable.

Passer d'une architecture plus orientée vers le back-end à quelque chose comme Next.js est un grand pas en avant. La courbe d'apprentissage peut être assez abrupte et, au départ, certains membres de l'équipe se sentaient vraiment en dehors de leur zone de confort. Les petits ajustements que nous avons faits, les leçons tirées de cet article, ont vraiment aidé à cela. De plus, l'expérience de développement avec Next.js donne un gain de productivité incroyable. Le cycle de retour des développeurs est incroyablement court !

Lectures complémentaires

  • "Passer à la production", Next.js Docs