Rendu sur le Web

L'une des principales décisions que doivent prendre les développeurs Web consiste à déterminer où implémenter la logique et l'affichage dans leur application. Cela peut être difficile car il existe de très façons de créer un site web.

Notre compréhension de cet espace s'appuie sur le travail que nous avons accompli au cours des dernières années dans Chrome pour communiquer avec des sites volumineux. De manière générale, nous encourageons les développeurs à envisager le rendu côté serveur ou le rendu statique plutôt qu'une approche de réhydratation complète.

Pour mieux comprendre les architectures que nous allons choisir lorsque nous prenons cette décision, nous avons besoin d'une solide compréhension de chaque approche et d'une terminologie cohérente à utiliser. Les différences entre les approches de rendu permettent d'illustrer les compromis liés au rendu sur le Web du point de vue des performances de la page.

Terminologie

Affichage

Rendu côté serveur
Effectuer le rendu d'une application côté client ou universelle au format HTML sur le serveur
Rendu côté client (CSR)
Afficher une application dans un navigateur en modifiant le DOM à l'aide de JavaScript
Réhydratation
"Démarrage" des vues JavaScript sur le client afin de réutiliser les données et l'arborescence DOM du code HTML rendu par le serveur.
Prérendu
Exécuter une application côté client au moment de la compilation pour capturer son état initial sous forme de code HTML statique

Performances

Temps de latence du premier octet (TTFB)
Délai entre le clic sur un lien et le chargement du premier octet de contenu sur la nouvelle page.
First Contentful Paint (FCP)
Heure à laquelle le contenu demandé (corps de l'article, etc.) devient visible.
Interaction to Next Paint (INP)
Métrique représentative qui évalue si une page répond rapidement et de manière cohérente aux entrées utilisateur.
Total Blocking Time (TBT)
Métrique de proxy pour INP qui calcule la durée pendant laquelle le thread principal a été bloqué pendant le chargement de la page.

Rendu côté serveur

Le rendu côté serveur génère le code HTML complet d'une page sur le serveur en réponse à la navigation. Cela évite des allers-retours supplémentaires pour l'extraction et la modélisation des données sur le client, car le moteur de rendu les gère avant que le navigateur n'obtienne une réponse.

Le rendu côté serveur produit généralement un FCP rapide. L'exécution de la logique de page et l'affichage sur le serveur vous permettent d'éviter d'envoyer beaucoup de code JavaScript au client. Cela permet de réduire la requête de navigation détaillée d'une page, ce qui peut également entraîner une baisse de l'INP, car le thread principal n'est pas bloqué aussi souvent lors du chargement de la page. Lorsque le thread principal est bloqué moins souvent, les interactions utilisateur ont plus de chances de s'exécuter plus tôt. C'est logique, car avec le rendu côté serveur, vous n'envoyez en fait que du texte et des liens au navigateur de l'utilisateur. Cette approche peut convenir à de nombreuses conditions d'appareils et de réseau, et offre des optimisations de navigateur intéressantes, telles que l'analyse de documents en flux continu.

Schéma illustrant le rendu côté serveur et l'exécution JS affectant FCP et TTI.
FCP et TTI avec rendu côté serveur

Avec le rendu côté serveur, les utilisateurs sont moins susceptibles d'attendre l'exécution du code JavaScript lié au processeur avant de pouvoir accéder à votre site. Même si vous ne pouvez pas éviter le JavaScript tiers, utiliser le rendu côté serveur pour réduire vos propres coûts JavaScript propriétaires peut vous donner un budget plus important pour le reste. Toutefois, cette approche présente un compromis potentiel : la génération de pages sur le serveur prend du temps, ce qui peut augmenter le TTFB de votre page.

Le fait que le rendu côté serveur soit suffisant pour votre application dépend en grande partie du type d'expérience que vous développez. Les bonnes applications du rendu côté serveur et du rendu côté client font l'objet d'un débat de longue date, mais vous pouvez toujours choisir d'utiliser le rendu côté serveur pour certaines pages et pas pour d'autres. Certains sites ont réussi à adopter des techniques de rendu hybride. Par exemple, le serveur Netflix affiche ses pages de destination relativement statiques, tout en préchargeant le code JavaScript pour les pages comportant beaucoup d'interactions, ce qui permet à ces pages plus lourdes affichées par le client de se charger rapidement.

De nombreux frameworks, bibliothèques et architectures modernes vous permettent d'afficher la même application à la fois sur le client et sur le serveur. Vous pouvez utiliser ces techniques pour le rendu côté serveur. Cependant, les architectures où le rendu s'effectue à la fois sur le serveur et sur le client constituent leur propre classe de solution, avec des caractéristiques de performances et des compromis très différents. Les utilisateurs de React peuvent utiliser les API DOM du serveur ou des solutions qui s'appuient dessus, comme Next.js, pour le rendu côté serveur. Les utilisateurs de Vue peuvent consulter le guide de rendu côté serveur ou Nuxt. Angular comporte la mention Universal. Toutefois, les solutions les plus populaires utilisent une certaine forme d'hydratation. Vous devez donc connaître les approches utilisées par votre outil.

Affichage statique

Le rendu statique s'effectue au moment de la compilation. Cette approche offre un FCP rapide, ainsi qu'une valeur de requête large et d'INP plus faible, tant que vous limitez la quantité de code JavaScript côté client sur vos pages. Contrairement au rendu côté serveur, il atteint également un TTFB constamment rapide, car le code HTML d'une page n'a pas besoin d'être généré dynamiquement sur le serveur. En règle générale, l'affichage statique consiste à produire un fichier HTML distinct pour chaque URL à l'avance. Grâce aux réponses HTML générées à l'avance, vous pouvez déployer des rendus statiques sur plusieurs CDN afin de tirer parti de la mise en cache périphérique.

Schéma illustrant l'affichage statique et l'exécution JS facultative affectant le FCP et le TTI.
FCP et TTI avec affichage statique.

Les solutions pour le rendu statique sont de toutes formes et de toutes tailles. Des outils tels que Gatsby sont conçus pour donner aux développeurs l'impression que leur application est affichée de manière dynamique, et non générée comme une étape de compilation. Les outils de génération de sites statiques tels que 11ty, Jekyll et Metalsmith adoptent un modèle statique et fournissent une approche davantage basée sur des modèles.

L'un des inconvénients de l'affichage statique est qu'il doit générer des fichiers HTML individuels pour chaque URL possible. Cela peut s'avérer difficile, voire impossible, lorsque vous ne pouvez pas prédire ces URL ou pour des sites comportant un grand nombre de pages uniques.

Les utilisateurs de React peuvent connaître Gatsby, l'exportation statique Next.js ou Navi, qui facilitent la création de pages à partir de composants. Cependant, l'affichage statique et le prérendu se comportent différemment: les pages affichées de manière statique sont interactives sans avoir à exécuter beaucoup de code JavaScript côté client, tandis que le prérendu améliore le FCP d'une application monopage qui doit être démarrée sur le client pour rendre les pages vraiment interactives.

Si vous ne savez pas si une solution donnée est l'affichage statique ou le prérendu, essayez de désactiver JavaScript et chargez la page que vous souhaitez tester. Pour les pages affichées de manière statique, la plupart des fonctionnalités interactives existent toujours sans JavaScript. Les pages prérendues peuvent toujours comporter certaines fonctionnalités de base telles que les liens avec JavaScript désactivé, mais la plupart de la page est inerte.

Un autre test utile consiste à utiliser la limitation du réseau dans les outils pour les développeurs Chrome et à déterminer le nombre de téléchargements JavaScript avant qu'une page ne devienne interactive. Le prérendu a généralement besoin de plus de JavaScript pour devenir interactif, et JavaScript a tendance à être plus complexe que l'approche d'amélioration progressive utilisée dans l'affichage statique.

Rendu côté serveur ou rendu statique

Le rendu côté serveur n'est pas la meilleure solution pour tout, car sa nature dynamique peut entraîner des coûts de calcul importants. De nombreuses solutions de rendu côté serveur ne vident pas les données plus tôt, ne retardent pas le TTFB ou ne doublent pas les données envoyées (par exemple, les états intégrés utilisés par JavaScript sur le client). Dans React, renderToString() peut être lent, car il est synchrone et à thread unique. Les API DOM du serveur React plus récentes acceptent le streaming, ce qui permet d'obtenir plus rapidement la partie initiale d'une réponse HTML au navigateur, tandis que le reste est encore généré sur le serveur.

Pour un rendu côté serveur "correct", vous devrez peut-être trouver ou créer une solution de mise en cache des composants, gérer la consommation de mémoire, utiliser des techniques de mémoisation, etc. Vous traitez ou recompilez souvent la même application deux fois, une fois sur le client et une fois sur le serveur. L'affichage côté serveur du contenu affiché rapidement ne vous demande pas forcément moins de travail. Si vous avez beaucoup de travail sur le client après l'arrivée d'une réponse HTML générée par le serveur sur le client, cela peut toujours entraîner une valeur de requête large et un INP plus élevés pour votre site Web.

L'affichage côté serveur génère du code HTML à la demande pour chaque URL. Toutefois, il peut être plus lent que de simplement diffuser un contenu statique. Si vous pouvez effectuer des étapes supplémentaires, le rendu côté serveur et la mise en cache HTML peuvent réduire considérablement le délai d'affichage du serveur. L'avantage du rendu côté serveur est la possibilité d'extraire plus de données "en direct" et de répondre à un ensemble de requêtes plus complet qu'avec le rendu statique. Les pages nécessitant une personnalisation sont un exemple concret de ce type de requête qui ne fonctionne pas bien avec l'affichage statique.

Le rendu côté serveur peut également présenter des décisions intéressantes lors de la création d'une PWA: est-il préférable d'utiliser la mise en cache pleine page du service worker ou simplement d'afficher des éléments de contenu individuels par le serveur ?

Rendu côté client

L'affichage côté client consiste à afficher les pages directement dans le navigateur avec JavaScript. Toute la logique, l'extraction de données, la modélisation et le routage sont gérés sur le client plutôt que sur le serveur. Le résultat final est que davantage de données sont transmises à l'appareil de l'utilisateur depuis le serveur, ce qui s'accompagne de ses propres compromis.

Le rendu côté client peut être difficile à créer et à maintenir rapide pour les appareils mobiles. Avec un peu d'effort pour maintenir un budget JavaScript serré et générer de la valeur avec le moins d'aller-retours possible possible, vous pouvez obtenir le rendu côté client afin de répliquer presque les performances du rendu pur côté serveur. Vous pouvez faire fonctionner l'analyseur plus rapidement en fournissant des données et des scripts essentiels à l'aide de <link rel=preload>. Nous vous recommandons également d'utiliser des modèles tels que PRPL pour vous assurer que les navigations initiales et ultérieures sont instantanées.

Schéma illustrant le rendu côté client affectant le FCP et le TTI.
FCP et TTI avec rendu côté client

Le principal inconvénient de l'affichage côté client est que la quantité de JavaScript requise tend à augmenter à mesure que l'application se développe, ce qui peut avoir un impact sur l'INP d'une page. Cela devient particulièrement difficile avec l'ajout de nouvelles bibliothèques JavaScript, de polyfills et de code tiers, qui sont en concurrence pour la puissance de traitement et doivent souvent être traités avant que le contenu d'une page puisse s'afficher.

Les expériences qui utilisent l'affichage côté client et reposent sur des bundles JavaScript volumineux doivent envisager une répartition agressive du code pour réduire la valeur de requête large et l'INP pendant le chargement de la page, ainsi que le chargement différé du code JavaScript pour ne diffuser que ce dont l'utilisateur a besoin, quand c'est nécessaire. Pour les expériences avec peu ou pas d'interactivité, le rendu côté serveur peut représenter une solution plus évolutive à ces problèmes.

Pour les personnes qui créent des applications monopages, l'identification des parties principales de l'interface utilisateur partagée par la plupart des pages vous permet d'appliquer la technique de mise en cache du shell d'application. Combinée aux service workers, cette approche peut considérablement améliorer les performances perçues lors des visites répétées, car la page peut charger très rapidement le code HTML de l'interface système de l'application et les dépendances à partir de CacheStorage.

La réhydratation combine rendu côté serveur et rendu côté client

La réhydratation est une approche qui tente de fluidifier les compromis entre le rendu côté client et côté serveur en faisant les deux. Les requêtes de navigation, telles que les chargements de page complète ou les rechargements de pages, sont gérées par un serveur qui affiche l'application en HTML, puis le code JavaScript et les données utilisées pour l'affichage sont intégrés dans le document obtenu. Lorsqu'elle est effectuée avec soin, cette méthode permet d'obtenir un FCP rapide, tel que le rendu côté serveur, puis "reprends" en s'affichant à nouveau sur le client. Cette solution est efficace, mais elle peut présenter des inconvénients considérables en termes de performances.

Le principal inconvénient du rendu côté serveur avec réhydratation est qu'il peut avoir un impact négatif significatif sur la table ToT et l'INP, même s'il améliore le FCP. Les pages affichées côté serveur peuvent sembler chargées et interactives, mais ne peuvent pas répondre aux entrées tant que les scripts côté client pour les composants n'ont pas été exécutés et que les gestionnaires d'événements n'ont pas été associés. Sur mobile, cela peut prendre plusieurs minutes, ce qui peut être source de confusion et de frustration pour l'utilisateur.

Un problème de réhydratation: une appli au prix de deux

Pour que le code JavaScript côté client "reprend" avec précision là où le serveur s'est arrêté, sans redemander toutes les données avec lesquelles le serveur a affiché son code HTML, la plupart des solutions de rendu côté serveur sérialisent la réponse des dépendances de données d'une interface utilisateur sous forme de tags de script dans le document. Comme cela duplique une grande quantité de code HTML, la réhydratation peut causer bien plus de problèmes que la simple interactivité différée.

Document HTML contenant une UI sérialisée, des données intégrées et un script bundle.js
Code en double dans le document HTML.

Le serveur renvoie une description de l'interface utilisateur de l'application en réponse à une requête de navigation, mais il renvoie également les données sources utilisées pour composer cette UI, ainsi qu'une copie complète de l'implémentation de l'interface utilisateur qui démarre ensuite sur le client. L'interface utilisateur ne devient interactive qu'une fois le chargement et l'exécution de bundle.js terminés.

Les métriques de performances collectées à partir de sites Web réels à l'aide du rendu côté serveur et de la réhydratation indiquent qu'il s'agit rarement de la meilleure option. La raison la plus importante est son effet sur l'expérience utilisateur, lorsqu'une page semble prête, mais qu'aucune de ses fonctionnalités interactives ne fonctionne.

Schéma illustrant l&#39;impact négatif du rendu client sur le TTI.
Effets de l'affichage côté client sur l'TTI.

Toutefois, il y a de l'espoir pour un rendu côté serveur avec une réhydratation. À court terme, seule l'utilisation du rendu côté serveur pour le contenu hautement mis en cache peut réduire le TTFB, produisant des résultats semblables au prérendu. Une réhydratation incrémentielle, progressive ou partielle peut être la clé pour rendre cette technique plus viable à l'avenir.

Afficher en streaming côté serveur et réhydrater progressivement

Le rendu côté serveur a connu un certain nombre de développements ces dernières années.

Le rendu côté serveur en flux continu vous permet d'envoyer du code HTML par segments que le navigateur peut afficher progressivement à mesure de sa réception. Le balisage est ainsi transmis plus rapidement à vos utilisateurs, ce qui accélère votre FCP. Dans React, les flux étant asynchrones dans renderToPipeableStream(), par rapport à renderToString() synchrones, la contre-pression est bien gérée.

La réhydratation progressive est également utile, et React l'a mise en œuvre. Avec cette approche, les parties individuelles d'une application affichée par le serveur sont "démarrées" au fil du temps, au lieu de l'approche courante actuelle qui consiste à initialiser l'ensemble de l'application en une seule fois. Cela peut vous aider à réduire la quantité de JavaScript nécessaire pour rendre les pages interactives, car cela vous permet de différer la mise à niveau côté client de parties de faible priorité de la page pour l'empêcher de bloquer le thread principal. Ainsi, les interactions utilisateur se produisent plus tôt après que l'utilisateur les a initiés.

La réhydratation progressive peut également vous aider à éviter l'un des pièges les plus courants liés à la réhydratation du rendu côté serveur: une arborescence DOM rendue par le serveur est détruite, puis reconstruite immédiatement, le plus souvent car le rendu synchrone initial côté client nécessitait des données qui n'étaient pas encore tout à fait prêtes, souvent un Promise qui n'a pas encore été résolu.

Réhydratation partielle

La réhydratation partielle s'est révélée difficile à mettre en œuvre. Cette approche est une extension de la réhydratation progressive qui analyse des parties individuelles de la page (composants, vues ou arbres) et identifie les parties avec peu d'interactivité ou pas de réactivité. Pour chacune de ces parties principalement statiques, le code JavaScript correspondant est ensuite transformé en références inertes et en caractéristiques décoratives, réduisant ainsi son encombrement côté client à presque zéro.

L'approche de l'hydratation partielle présente ses propres problèmes et compromis. Cela pose des défis intéressants pour la mise en cache, et la navigation côté client signifie que nous ne pouvons pas supposer que le code HTML affiché par le serveur pour les parties inertes de l'application est disponible sans chargement complet de la page.

Rendu trisomorphique

Si vous avez la possibilité d'utiliser des service workers, envisagez d'effectuer un rendu trisomorphique. Cette technique vous permet d'utiliser le rendu côté serveur en streaming pour les navigations initiales ou autres que JavaScript, puis de demander à votre service worker d'afficher le code HTML pour les navigations après son installation. Cela permet de maintenir les composants et les modèles mis en cache à jour, et d'activer les navigations de type SPA pour afficher de nouvelles vues dans la même session. Cette approche fonctionne mieux lorsque vous pouvez partager le même code de création de modèles et de routage entre le serveur, la page du client et le service worker.

Schéma du rendu trisomorphique montrant un navigateur et un service worker en communication avec le serveur.
Schéma du fonctionnement du rendu trisomorphe.

Considérations relatives au SEO

Lorsqu'elles choisissent une stratégie de rendu Web, les équipes tiennent souvent compte de l'impact du SEO. Le rendu côté serveur est un choix populaire pour offrir une expérience "complète" que les robots d'exploration peuvent interpréter. Les robots d'exploration peuvent comprendre JavaScript, mais leur affichage est souvent limité. Le rendu côté client peut fonctionner, mais nécessite souvent des tests supplémentaires et des frais supplémentaires. Plus récemment, l'affichage dynamique est également devenu une option utile si votre architecture dépend fortement du code JavaScript côté client.

En cas de doute, l'outil de test d'optimisation mobile est un excellent moyen de vérifier que l'approche que vous avez choisie répond à vos attentes. Il permet au robot d'exploration Google d'afficher un aperçu de n'importe quelle page, du contenu HTML sérialisé qu'il trouve après l'exécution du script JavaScript et des erreurs rencontrées lors de l'affichage.

Capture d&#39;écran de l&#39;interface utilisateur du test d&#39;optimisation mobile.
UI de test d'optimisation mobile

Conclusion

Lorsque vous décidez d'une approche pour le rendu, mesurez et identifiez les goulots d'étranglement. Déterminez si le rendu statique ou côté serveur peut vous permettre d'atteindre cet objectif. Il est acceptable de diffuser principalement du code HTML avec un minimum de JavaScript pour une expérience interactive. Voici une infographie pratique montrant le spectre serveur-client:

Infographie montrant l&#39;éventail des options décrites dans cet article.
Options de rendu et avantages associés

Crédits

Merci à tous pour leurs avis et leurs sources d'inspiration:

Jeffrey Posnick, Houssein Djirdeh, Shubhie Panicker, Chris Harrelson et Sebastian Markbåge