Passer à Next.js et accélérer 7,5 fois le chargement de la page d'accueil de manifold.co

Aujourd'hui, nous publions une traduction d'une histoire sur la façon dont la transition de React Boilerplate à Next.js , un cadre pour développer des applications Web progressives basées sur React, a accéléré 7,5 fois le chargement de la page d' accueil du projet manifold.co. Aucun autre changement n'a été apporté au projet, et cette transition, en général, s'est révélée complètement invisible pour les autres parties du système. Ce qui s'est finalement avéré être encore meilleur que prévu.



Aperçu des résultats


En fait, nous pouvons dire que la transition vers Next.js nous a donné quelque chose comme «une augmentation de la productivité du projet qui est venue de nulle part». Voici à quoi ressemble le temps de chargement du projet lors de l'utilisation de diverses ressources matérielles et connexions réseau.
Connexion
CPU
À secondes
Après quelques secondes
% D'amélioration
Rapide (200 Mbps)
Rapide
1,5
0,2
750
Moyen (3G)
Rapide
5.6
1.1
500
Moyen (3G)
Moyen
7,5
1,3
570
Lent (connexion 3G lente)
Moyen
22
4
550

Lorsque vous utilisez une connexion rapide et un appareil doté d'un processeur rapide, le temps de chargement du site passe de 1,5 s. jusqu'à 0,2 s., c'est-à-dire que cet indicateur s'est amélioré 7,5 fois. Sur une connexion de qualité moyenne et sur un appareil aux performances moyennes, le temps de chargement du site est passé de 7,5 s. jusqu'à 1,3 s

Que se passe-t-il lorsqu'un utilisateur clique sur une URL?


Afin de comprendre les fonctionnalités du travail des applications web progressives (Progressive Web App, PWA), vous devez d'abord comprendre ce qui se passe entre le moment où l'utilisateur clique sur l'URL (à l'adresse de notre site) et le moment où il voit quelque chose dans une fenêtre de navigateur (dans ce cas, notre application React).


Étapes d'application

Considérez les 5 étapes de travail avec l'application, dont le schéma est donné ci-dessus.

  1. L'utilisateur accède à l'URL, le système découvre l'adresse du serveur à l'aide de DNS et accède au serveur. Tout cela se fait extrêmement rapidement, prenant généralement moins de 100 millisecondes, mais cette étape prend un certain temps, c'est pourquoi elle est mentionnée ici.
  2. Maintenant, le serveur renvoie le code HTML de la page, mais la page dans le navigateur reste vide jusqu'à ce que les ressources nécessaires à son affichage soient chargées (sauf si les ressources sont chargées de manière asynchrone ). En fait, à ce stade, plus d'actions ont lieu que ce qui est indiqué dans le diagramme, mais un examen conjoint de tous ces processus nous conviendra également.
  3. Après avoir chargé le code HTML et les ressources les plus importantes, le navigateur commence à afficher ce qu'il peut afficher, en continuant à charger tout le reste (images, par exemple) en arrière-plan. Vous êtes-vous déjà demandé pourquoi des images «surgissent» soudainement sur une page de manière évidente plus rapidement que nécessaire, et parfois se chargent trop longtemps? C'est précisément pourquoi cela se produit. Cette approche vous permet de créer rapidement une page terminée.
  4. Le code JavaScript ne peut être analysé et exécuté qu'après son chargement. Selon la taille du code JS utilisé sur la page (et cela peut être, pour une application React typique, assez volumineux si le code est conditionné dans un seul fichier) cela peut prendre plusieurs secondes ou même plus (notez que JS le code n'a pas besoin, pour commencer à exécuter, d'attendre le chargement de toutes les autres ressources, malgré le fait que sur le diagramme il ressemble exactement à ça).
  5. Dans le cas d'une application React, le moment vient maintenant où le code modifie le DOM, ce qui oblige le navigateur à redessiner la page déjà affichée. Ensuite, un autre cycle de chargement des ressources commence. Le temps que prendra cette étape dépendra de la complexité de la page.

Plus vite, mieux c'est.


Puisqu'une application Web progressive prend du code React et produit du code HTML et CSS statique, cela signifie que l'utilisateur voit l'application React déjà à l'étape 3 du schéma ci-dessus, et non à l'étape 5. Dans nos tests, cela prend 0,2-4 secondes , qui dépend de la vitesse de la connexion de l'utilisateur à Internet et de son appareil. C'est beaucoup mieux que les 1,5-22 secondes précédentes. Les applications Web progressives sont un moyen fiable de fournir des applications React plus rapidement à l'utilisateur.

La raison pour laquelle les applications Web progressives et les cadres associés comme Next.js ne sont toujours pas très populaires est que, traditionnellement, les cadres JS ne réussissent pas particulièrement à générer du code HTML statique. Aujourd'hui, tout a beaucoup changé en raison du fait que les frameworks tels que React, Vue et Angular, et d'autres, ont un excellent support pour le rendu côté serveur. Cependant, pour utiliser ces outils, vous avez toujours besoin d'une compréhension approfondie des fonctionnalités du travail des bundlers et des outils de construction de projets. Travailler avec tout cela n'est pas sans problème.

L'émergence récente de cadres PWA tels que Next.js et Gatsby (tous deux apparus fin 2016 - début 2017) a constitué une étape importante vers l'adoption généralisée de PWA en raison de la réduction des barrières à l'entrée et du fait qu'il a rendu l'utilisation de ces cadres une tâche simple et agréable.

Bien que toutes les applications ne puissent pas être transférées vers Next.js, pour de nombreuses applications React, cette transition signifie les mêmes «performances de nulle part» dont nous parlons ici, complétées par une utilisation encore plus efficace des ressources réseau.

Est-il difficile de migrer vers Next.js?


En général, on peut noter que la traduction de notre page d'accueil en Next.js n'a pas été très difficile. Cependant, nous avons rencontré des difficultés dues aux caractéristiques d'architecture de notre application.

▍ Rejeter un routeur React


Nous avons dû abandonner le routeur React car Next.js a son propre routeur intégré, qui est mieux combiné avec des optimisations concernant la séparation de code effectuées au-dessus de l'architecture PWA. Cela permet à ce routeur de fournir un chargement de page beaucoup plus rapide que ce à quoi vous vous attendez de n'importe quel routeur côté client.

Le routeur Next.js est un peu un routeur React à haute vitesse, mais ce n'est toujours pas un routeur React.

En pratique, comme nous n'avons pas profité des fonctionnalités particulièrement avancées qu'offre le routeur React, la transition vers le routeur Next.js pour nous consistait simplement à remplacer le composant routeur React standard par le composant Next.js correspondant:

/*   ( React) */ <Link to="/my/page">  A link </Link> /*   ( Next.js) */ <Link href="/my/page" passHref>  <a>    A link  </a> </Link> 

En général, tout s'est avéré pas si mal. Nous avons dû renommer la propriété et ajouter une balise à des fins de rendu du serveur. Comme nous avons également utilisé la bibliothèque de styled-components , il s'est avéré que dans la plupart des cas, nous devions ajouter la propriété passHref afin de garantir que le système se comporte de telle sorte que href pointe toujours vers la balise générée.


Demandes de réseau pour manifold.co

Afin de voir de vos propres yeux l'optimisation du routeur Next.js en action, ouvrez l'onglet Réseau des outils de développement du navigateur en affichant la page manifold.co et cliquez sur un lien. La figure précédente montre le résultat du clic sur le lien /services . Comme vous pouvez le voir, cela conduit à l'exécution d'une requête de chargement de services.js au lieu d'exécuter une requête régulière.

Je ne parle pas seulement du routage côté client; le routeur React convient également pour résoudre ce problème. Je parle d'un vrai morceau de code JavaScript qui a été extrait du reste du code et chargé sur demande. Cela se fait en utilisant Next.js. standard Et c'est bien mieux que ce que nous avions avant. À savoir, nous parlons d'un gros paquet de code JS d'une taille de 1,7 Mo, que le client, avant de voir quelque chose, devait télécharger et traiter.

Bien que la solution présentée ici ne soit pas parfaite, elle est beaucoup plus proche que la précédente de l'idée que les utilisateurs ne téléchargent que du code pour les pages qu'ils consultent.

▍ Caractéristiques de l'utilisation de Redux


Poursuivant le sujet des difficultés liées à la transition vers Next.js, on peut noter que toutes les optimisations intéressantes que Next.js subit à l'application ont un certain impact sur cette application. À savoir, puisque Next.js effectue une séparation de code au niveau de la page, il empêche le développeur d'accéder au composant racine React ou à la méthode render() de la bibliothèque react-dom . Si vous avez déjà été impliqué dans la configuration de Redux, vous pouvez noter que tout cela nous dit que pour un fonctionnement normal avec Redux, nous devons résoudre le problème, c'est-à-dire qu'il n'est pas clair exactement où chercher Redux.

Next.js fournit un composant spécial d'ordre supérieur, withRedux , qui withRedux comme un wrapper pour tous les composants de niveau supérieur sur chaque page:

 export default withRedux(HomePage); 

Bien que tout cela ne soit pas si mal, mais si vous avez besoin de méthodes createStore() , comme lors de l'utilisation d' injecteurs redux-reducer , attendez-vous à ce que vous ayez besoin de plus de temps pour déboguer le wrapper (et d'ailleurs, n'essayez jamais utiliser quelque chose comme redux-reducer-injectors ).

De plus, du fait que Redux est désormais une «boîte noire», l'utilisation de la bibliothèque Immutable avec elle devient problématique. Bien que le fait qu'Immutable fonctionnera avec Redux semble assez évident, j'ai rencontré un problème. Par conséquent, soit l'état de niveau supérieur n'était pas immuable (le get is not a function erreur de get is not a function ), soit le composant wrapper a essayé d'utiliser la notation par points pour fonctionner avec les objets JS au lieu de la méthode .get() ( Can't get catalog of undefined erreurs Can't get catalog of undefined ). Pour déboguer ce problème, j'ai dû me référer au code source. Après tout, Next.js oblige le développeur à utiliser ses propres mécanismes pour une raison.

En général, on peut noter que le principal problème associé à Next.js est que très peu dans ce cadre est bien documenté. Il existe de nombreux exemples dans la documentation sur la base desquels vous pouvez créer votre propre création, mais s'il n'y en a pas qui reflète les caractéristiques de votre projet, vous ne pouvez que souhaiter bonne chance.

▍Récupérer le rejet


Nous avons utilisé la bibliothèque react-inlinesvg , qui offre des options de style pour les images SVG intégrées et la mise en cache des requêtes. Mais ici, nous avons eu un problème: lors du rendu du serveur, les requêtes XHR n'existent pas (du moins pas dans le sens des URL générées par Webpack, comme vous pouvez vous y attendre). Les tentatives d'exécution de telles requêtes interfèrent avec le rendu du serveur.

Bien qu'il existe d'autres bibliothèques pour travailler avec des données SVG intégrées qui prennent en charge SSR, j'ai décidé d'abandonner cette fonctionnalité, car les fichiers SVG étaient encore rarement utilisés. Je les ai soit remplacés par des images ordinaires, des balises <img> , si je n'avais pas besoin de style lors de l'affichage des images correspondantes, soit je les ai incorporés dans le code sous la forme de React JSX. Probablement, tout s'est encore amélioré, car les illustrations JSX ont maintenant frappé le navigateur lorsque la page a été chargée pour la première fois, et le bundle JS envoyé au client avait 1 bibliothèque de moins.

Si vous devez utiliser des mécanismes de chargement de données (j'avais besoin de cette fonctionnalité pour une autre bibliothèque), vous pouvez le configurer avec next.config.js , en utilisant whatwg-fetch et node-fetch :

 module.exports = { webpack: (config, options) =>   Object.assign(config, {     plugins: config.plugins.concat([       new webpack.ProvidePlugin(         config.isServer           ? {}           : { fetch: 'imports-loader?this=>global!exports-loader?global.fetch!whatwg-fetch' }       ),     ]),   resolve: Object.assign(config.resolve, {     alias: Object.assign(       config.resolve.alias,       config.isServer ? {} : { fetch: 'node-fetch' }     ),   }), }), }; 

▍ Client et serveur JS


La dernière caractéristique de Next.js, que je voudrais mentionner ici, est que ce cadre est lancé deux fois - une fois pour le serveur, et encore pour le client. Cela brouille légèrement la ligne entre le code JavaScript côté client et le code Node.js dans la même base de code, provoquant des erreurs inhabituelles comme fs is undefined lorsque vous essayez de tirer parti des fonctionnalités Node.js sur le client.

En conséquence, nous devons construire de telles constructions dans next.js.config :

 module.exports = { webpack: (config, options) =>   Object.assign(config, {     node: config.isServer ? undefined : { fs: 'empty' },   }), }; 

L'indicateur config.isServer dans Webpack sera votre meilleur ami si vous devez exécuter le même code dans des environnements différents.

En outre, Next.js prend en charge, en plus des méthodes standard pour le cycle de vie des composants React, la méthode getInitialProps() , qui est appelée uniquement lorsque le code getInitialProps() en mode serveur:

 class HomePage extends React.Component { static getInitialProps() {   //         } componentDidMount() {   //     ,    } … } 

Oui, et n'oublions pas que notre bon ami, l'objet window , nécessaire pour organiser l'écoute des événements, pour déterminer la taille de la fenêtre du navigateur et donner accès à de nombreuses fonctions utiles, n'est pas disponible dans Node.js:

 if (typeof window !== 'undefined') { // ,     `window`      } 

Il convient de noter que même Next.js n'est pas en mesure de sauver le développeur de la nécessité de résoudre les problèmes associés à l'exécution du même code sur le serveur et sur le client. Mais pour résoudre de tels problèmes, config.isServer et getInitialProps() sont très utiles.

Résultats: que se passera-t-il après Next.js?


À court terme, le framework Next.js correspond parfaitement, en termes de performances, à nos exigences de rendu de serveur et à la possibilité de visualiser notre site sur des appareils sur lesquels JavaScript est désactivé. De plus, il vous permet désormais d'utiliser des méta-balises avancées (riches).

À l'avenir, nous envisagerons peut-être d'autres options dans le cas où notre application nécessite à la fois un rendu de serveur et une logique de serveur plus complexe (par exemple, nous examinons la possibilité de mettre en œuvre une technologie d'authentification unique sur manifold.co et dashboard.manifold.co ) Mais jusque-là, nous utiliserons Next.js, car ce cadre, avec de petits coûts de temps, nous a apporté d'énormes avantages.

Chers lecteurs! Utilisez-vous Next.js dans vos projets?

Source: https://habr.com/ru/post/fr442654/


All Articles