Accélérer le frontend. Quand beaucoup de demandes de serveur sont bonnes

Cet article décrit certaines méthodes pour accélérer le chargement des applications frontales afin d'implémenter une interface utilisateur rapide et réactive.

Nous discuterons de l'architecture générale du frontend, comment précharger les ressources nécessaires et augmenter la probabilité qu'elles soient dans le cache. Nous verrons un peu comment donner des ressources depuis le backend et quand il est possible de se limiter à des pages statiques au lieu d'une application cliente interactive.

Le processus de téléchargement est divisé en trois étapes. Pour chaque étape, nous formulons des stratégies générales pour augmenter la productivité:

  1. Rendu initial : combien de temps faut-il à l'utilisateur pour voir au moins quelque chose
    • Réduisez les demandes de blocage du rendu
    • Évitez les chaînes séquentielles
    • Réutiliser les connexions au serveur
    • Travailleurs de service pour le rendu instantané
  2. : ,
    • . .
    • ,


  3. :
    • ,


Jusqu'au rendu initial, l'utilisateur ne voit rien à l'écran. De quoi avons-nous besoin pour ce rendu? Au minimum, téléchargez un document HTML et, dans la plupart des cas, des ressources supplémentaires, telles que des fichiers CSS et JavaScript. Une fois qu'ils sont disponibles, le navigateur peut commencer une sorte de rendu. Les

graphiques WebPageTest sont fournis tout au long de cet article . La séquence de requêtes pour votre site ressemblera probablement à ceci.



Le document HTML charge un tas de fichiers supplémentaires et la page est rendue après leur téléchargement. Veuillez noter que les fichiers CSS sont chargés en parallèle les uns aux autres, donc chaque demande supplémentaire n'ajoute pas de retard significatif.

(Remarque: dans la capture d'écran, gov.uk est un exemple où HTTP / 2 est maintenant activéafin que le domaine de ressources puisse réutiliser une connexion existante. Voir ci-dessous pour les connexions au serveur.)

Réduisez les demandes de blocage du rendu


Les feuilles de style et (par défaut) les scripts bloquent le rendu de tout contenu en dessous.

Il existe plusieurs options pour résoudre ce problème:

  • Déplacez les balises de script au bas du corps
  • Téléchargez des scripts en mode asynchrone en utilisant async
  • Si JS ou CSS doivent être chargés séquentiellement, il est préférable de les incorporer avec de petits extraits

Évitez les conversations avec des requêtes séquentielles qui bloquent le rendu


Le retard dans le rendu du site n'est pas nécessairement associé à un grand nombre de requêtes qui bloquent le rendu. Plus important est la taille de chaque ressource, ainsi que l'heure de début de son téléchargement. Autrement dit, le moment où le navigateur se rend soudain compte que cette ressource doit être téléchargée.

Si le navigateur détecte la nécessité de télécharger le fichier uniquement après avoir terminé une autre demande, il existe une chaîne de demandes. Il peut se former pour diverses raisons:

  • Règles @importCSS
  • Polices Web référencées par le fichier CSS
  • Balises JavaScript ou script téléchargeables

Jetez un œil à cet exemple: l'



un des fichiers CSS de ce site charge la police Google via la règle @import. Cela signifie que le navigateur doit exécuter à tour de rôle les requêtes suivantes:

  1. Document HTML
  2. Applications CSS
  3. CSS pour les polices Google
  4. Fichier Google Font Woff (non illustré dans le diagramme)

Pour résoudre ce problème, déplacez d'abord la demande CSS Google Fonts de la balise @importvers le lien dans le document HTML. Nous raccourcissons donc la chaîne d'un maillon.

Pour accélérer encore plus les choses, intégrez Google Fonts CSS directement dans votre fichier HTML ou CSS.

(Gardez à l'esprit que la réponse CSS du serveur Google Fonts dépend de la ligne d'agent utilisateur. Si vous faites une demande à l'aide d'IE8, le CSS fera référence au fichier EOT, le navigateur IE11 recevra le fichier woff et les navigateurs modernes recevront le fichier woff2. Si vous acceptez que les anciens navigateurs seront limités aux polices système, vous pouvez simplement copier et coller le contenu du fichier CSS pour vous-même).

Même après le début du rendu, il est peu probable que l'utilisateur puisse interagir avec la page, car la police doit être chargée pour afficher le texte. Il s'agit d'un retard de réseau supplémentaire que j'aimerais éviter. Le paramètre swap est utile ici , il vous permet de l'utiliser font-displayavec Google Fonts et de stocker des polices localement.

Parfois, la chaîne de requête ne peut pas être résolue. Dans de tels cas, vous pouvez envisager la balise de précharge ou de préconnexion . Par exemple, le site Web de l'exemple ci-dessus peut se connecter fonts.googleapis.comavant l'arrivée de la demande CSS réelle.

Réutilisation des connexions serveur pour accélérer les demandes


Pour établir une nouvelle connexion au serveur, nécessite généralement trois échanges de paquets entre le navigateur et le serveur:

  1. Recherche DNS
  2. Établir une connexion TCP
  3. Établir une connexion SSL

Une fois la connexion établie, au moins un autre échange de paquets est requis pour envoyer une demande et recevoir une réponse.

Le tableau ci - dessous montre que nous initions une connexion avec quatre serveurs différents: hostgator.com, optimizely.com, googletagmanager.comet googelapis.com.

Cependant, les demandes de serveur suivantes peuvent réutiliser une connexion existante . Le téléchargement base.csssoit index1.cssarrive plus vite parce qu'ils sont situés sur le même serveur hostgator.comavec lequel une connexion a déjà été établie.



Réduisez la taille des fichiers et utilisez CDN


Vous contrôlez deux facteurs qui affectent le temps d'exécution de la requête: la taille des fichiers de ressources et l'emplacement des serveurs.

Envoyez le moins de données possible à l'utilisateur et assurez-vous qu'elles sont compressées (par exemple, en utilisant brotli ou gzip).

Les réseaux de distribution de contenu (CDN) ont des serveurs partout dans le monde. Au lieu de se connecter à un serveur central, un utilisateur peut se connecter à un serveur CDN plus proche. Ainsi, l'échange de paquets sera beaucoup plus rapide. Ceci est particulièrement adapté aux ressources statiques telles que CSS, JavaScript et les images, car elles sont faciles à distribuer via CDN.

Éliminez la latence du réseau avec les employés de service


Les techniciens de maintenance vous permettent d'intercepter les demandes avant de les envoyer au réseau. Cela signifie que la réponse vient presque instantanément !



Bien sûr, cela ne fonctionne que si vous n'avez vraiment pas besoin de recevoir de données du réseau. La réponse doit déjà être mise en cache, donc l'avantage n'apparaîtra qu'à partir du deuxième téléchargement d'application.

L'agent de service ci-dessous met en cache le code HTML et CSS nécessaire pour rendre la page. Lorsque l'application se charge à nouveau, elle essaie d'émettre elle-même les ressources mises en cache - et accède au réseau uniquement si elles ne sont pas disponibles.

self.addEventListener("install", async e => {
 caches.open("v1").then(function (cache) {
   return cache.addAll(["/app", "/app.css"]);
 });
});

self.addEventListener("fetch", event => {
 event.respondWith(
   caches.match(event.request).then(cachedResponse => {
     return cachedResponse || fetch(event.request);
   })
 );
});

Dans ce guide, expliqué en détail sur l'utilisation des services pour précharger et mettre en cache les ressources.

Téléchargez l'application


Ainsi, l'utilisateur voit quelque chose à l'écran. Quelles autres étapes sont nécessaires pour qu'il utilise l'application?

  1. Télécharger le code d'application (JS et CSS)
  2. Téléchargez les données requises pour la page
  3. Téléchargez des données et des images supplémentaires



Veuillez noter que non seulement le téléchargement de données à partir du réseau peut retarder le rendu. Une fois votre code chargé, le navigateur doit l'analyser, le compiler et l'exécuter.

Téléchargez uniquement le code nécessaire et maximisez le nombre de hits dans le cache


«Casser un package» signifie télécharger uniquement le code nécessaire pour la page en cours, pas l'application entière. Cela signifie également que des parties du package peuvent être mises en cache, même si d'autres parties ont changé et doivent être rechargées.

En règle générale, le code est divisé en plusieurs parties:

  • Code pour une page spécifique (spécifique à la page)
  • Code d'application commun
  • Modules tiers qui changent rarement (idéal pour la mise en cache!)

Webpack peut automatiquement effectuer cette optimisation, casser le code et réduire le poids total de la charge. Le code est divisé en morceaux à l'aide de l'objet optimisation.splitChunks . Séparez le runtime (runtime) dans un fichier séparé: de cette façon, vous pouvez bénéficier d'une mise en cache à long terme. Ivan Akulov a écrit un guide détaillé sur la décomposition d'un package en fichiers séparés et la mise en cache dans Webpack .

Il n'est pas possible d'allouer automatiquement du code pour une page spécifique. Vous devez identifier manuellement les pièces pouvant être téléchargées séparément. Il s'agit souvent d'un chemin ou d'un ensemble de pages spécifique. Utilisez des importations dynamiques pour charger paresseusement ce code.

La division du package global en parties augmentera le nombre de demandes de téléchargement de votre application. Mais ce n'est pas un gros problème si les requêtes sont exécutées en parallèle, surtout si le site est chargé en utilisant le protocole HTTP / 2. Vous pouvez le voir pour les trois premières requêtes dans le diagramme suivant:



Cependant, deux requêtes consécutives sont également visibles dans le diagramme. Ces fragments sont nécessaires uniquement pour cette page particulière et ils sont chargés dynamiquement import().

Vous pouvez essayer de résoudre le problème en insérant une précharge de balise de précharge .



Mais nous constatons que le temps total de chargement des pages a augmenté.

Le préchargement des ressources est parfois contre-productif car il retarde le chargement de fichiers plus importants. LisArticle d'Andy Davis sur le préchargement des polices et comment cette procédure bloque le début du rendu de page.

Chargement de données pour une page


Votre application devrait probablement afficher des données. Voici quelques conseils que vous pouvez utiliser pour télécharger ces données tôt sans délais de rendu inutiles.

N'attendez pas le téléchargement complet du package avant de commencer le téléchargement des données


Voici un cas particulier d'une chaîne de requêtes séquentielles: vous téléchargez l'intégralité du package d'application, puis ce code demande les données nécessaires pour la page.

Il y a deux façons d'éviter cela:

  1. Incorporer des données dans un document HTML
  2. Exécutez une demande de données à l'aide du script intégré dans le document

L'incorporation des données dans HTML garantit que l'application n'attend pas son chargement. Cela réduit également la complexité du système, car vous n'avez pas besoin de gérer l'état de démarrage.

Cependant, ce n'est pas une bonne idée si une telle technique retarde le rendu initial.

Dans ce cas, ainsi que si vous soumettez un document HTML mis en cache via un technicien de service, vous pouvez utiliser le script intégré pour télécharger ces données comme alternative. Vous pouvez le rendre disponible en tant qu'objet global, voici une promesse:

window.userDataPromise = fetch("/me")

Si les données sont prêtes et dans une telle situation, l'application peut immédiatement démarrer le rendu ou attendre qu'elle soit prête.

Lorsque vous utilisez les deux méthodes, vous devez savoir à l'avance quelles données la page va charger avant que l'application ne commence le rendu. Cela est généralement évident pour les données liées à l'utilisateur (nom d'utilisateur, notifications, etc.), mais plus difficile avec un contenu spécifique à une page particulière. Il est peut-être judicieux de mettre en évidence les pages les plus importantes et d'écrire votre propre logique pour elles.

Ne bloquez pas le rendu en attendant des données non pertinentes


Parfois, pour générer des données, vous devez exécuter une logique complexe lente sur le backend. Dans de tels cas, vous pouvez d'abord essayer de télécharger une version plus simple des données, si cela suffit pour rendre l'application fonctionnelle et interactive.

Par exemple, un outil d'analyse peut d'abord télécharger une liste de tous les graphiques avant de charger des données. Cela permet à l'utilisateur de rechercher immédiatement le diagramme qui l'intéresse, et aide également à distribuer les requêtes backend à différents serveurs.



Évitez les requêtes de données consécutives


Cela peut contredire le paragraphe précédent selon lequel il est préférable de publier des données non essentielles dans une demande distincte. Par conséquent, il convient de clarifier: éviter les chaînes avec des demandes de données séquentielles, si chaque demande terminée ne conduit pas au fait que l'utilisateur dispose de plus d'informations .

Au lieu de demander d'abord quel utilisateur est connecté, puis de demander une liste de ses groupes, renvoyez immédiatement la liste des groupes avec les informations sur l'utilisateur dans la première requête. Vous pouvez utiliser GraphQL pour cela , mais le point de terminaison user?includeTeams=truefonctionne également très bien.

Rendu côté serveur


Le rendu côté serveur signifie le pré-rendu de l'application, donc un code HTML pleine page est renvoyé à la demande du client. Le client voit la page complètement restituée, sans attendre le chargement de code ou de données supplémentaires!

Étant donné que le serveur envoie au client uniquement du code HTML statique, l'application n'est pas interactive à ce stade. Vous devez télécharger l'application elle-même, démarrer la logique de rendu, puis connecter les écouteurs d'événements nécessaires au DOM.

Utilisez le rendu côté serveur si la visualisation de contenu non interactif est précieuse en soi. Il est également agréable de mettre en cache le HTML rendu sur le serveur et de le renvoyer immédiatement à tous les utilisateurs sans délai. Par exemple, le rendu côté serveur est idéal lorsque vous utilisez React pour afficher des articles de blog.

ÀCet article de Mikhail Yanashek décrit comment combiner les travailleurs de service et le rendu côté serveur.

Page suivante


À un moment donné, l'utilisateur s'apprête à appuyer sur un bouton et à passer à la page suivante. À partir du moment où vous ouvrez la page de démarrage, vous contrôlez ce qui se passe dans le navigateur, vous pouvez donc vous préparer à la prochaine interaction.

Préchargement des ressources


Si vous préchargez le code nécessaire pour la page suivante, le délai disparaît lorsque l'utilisateur démarre la navigation. Utilisez des balises de prélecture ou webpackPrefetchpour l'importation dynamique:

import(
    /* webpackPrefetch: true, webpackChunkName: "todo-list" */ "./TodoList"
)

Considérez le type de charge que vous placez sur l'utilisateur en termes de trafic et de bande passante, surtout s'il est connecté via une connexion mobile. Si une personne a téléchargé la version mobile du site et que le mode de stockage des données est actif, il est raisonnable de précharger de manière moins agressive.

Réfléchissez stratégiquement aux parties de l'application dont l'utilisateur aura besoin auparavant.

Réutilisation de données déjà téléchargées


Mettez les données en cache localement dans votre application et utilisez-les pour éviter de futures demandes. Si l'utilisateur passe de la liste de ses groupes à la page «Modifier le groupe», vous pouvez effectuer la transition instantanément, en réutilisant les données précédemment téléchargées sur le groupe.

Veuillez noter que cela ne fonctionnera pas si l'objet est souvent modifié par d'autres utilisateurs et que les données téléchargées peuvent devenir obsolètes. Dans ces cas, il existe une option pour afficher d'abord les données en lecture seule existantes tout en exécutant simultanément une demande de données mises à jour.

Conclusion


Cet article répertorie un certain nombre de facteurs qui peuvent ralentir votre page à différentes étapes du processus de chargement. Des outils tels que Chrome DevTools , WebPageTest et Lighthouse vous aideront à déterminer lesquels de ces facteurs affectent votre application.

Dans la pratique, l'optimisation va rarement immédiatement dans toutes les directions. Nous devons découvrir ce qui a le plus d'impact sur les utilisateurs et nous concentrer sur cela.

Pendant que j'écrivais l'article, j'ai réalisé une chose importante: j'avais une croyance bien ancrée que de nombreuses demandes de serveur individuelles étaient mauvaises pour les performances. C'était le cas dans le passé, lorsque chaque demande nécessitait une connexion distincte, et les navigateurs n'autorisaient que quelques connexions par domaine. Mais avec HTTP / 2 et les navigateurs modernes, ce n'est plus le cas.

Il existe de bons arguments en faveur de la division de l'application en plusieurs parties (avec multiplication des requêtes). Cela vous permet de télécharger uniquement les ressources nécessaires et il est préférable d'utiliser le cache, car seuls les fichiers modifiés devront être rechargés.

All Articles