L'une des décisions fondamentales que les développeurs Web doivent prendre est de choisir où implémenter la logique et le rendu dans leur application. Cela peut être difficile, car il existe de nombreuses façons de créer un site Web.
Notre compréhension de cet espace est basée sur notre travail dans Chrome avec de grands sites au cours des dernières années. De manière générale, nous encourageons les développeurs à envisager l'affichage côté serveur ou l'affichage statique plutôt qu'une approche de réhydratation complète.
Pour mieux comprendre les architectures parmi lesquelles nous choisissons lorsque nous prenons cette décision, nous avons besoin d'une terminologie cohérente et d'un framework partagé pour chaque approche. Vous pourrez ensuite mieux évaluer les compromis de chaque approche de rendu du point de vue des performances des pages.
Terminologie
Commençons par définir certains termes que nous allons utiliser.
Affichage
- Affichage côté serveur
- Affichage d'une application sur le serveur pour envoyer du code HTML au client plutôt que du code JavaScript.
- Affichage côté client (CSR)
- Rendu d'une application dans un navigateur, à l'aide de JavaScript pour modifier le DOM.
- Prerendering
- Exécuter une application côté client au moment de la compilation pour capturer son état initial en tant que code HTML statique.
- Hydratation
- Exécuter des scripts côté client pour ajouter un état d'application et de l'interactivité au code HTML rendu par le serveur. L'hydratation suppose que le DOM ne change pas.
- Réhydratation
- Bien qu'il soit souvent utilisé comme synonyme d'hydratation, le terme "réhydratation" implique une mise à jour régulière du DOM avec le dernier état, y compris après l'hydratation initiale.
Performances
- Time to First Byte (TTFB)
- Temps écoulé entre le moment où l'utilisateur clique sur un lien et le moment où le premier octet de contenu se charge 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 proxy pour l'INP qui calcule la durée pendant laquelle le thread principal a été bloqué lors du chargement de la page.
Affichage côté serveur
L'affichage côté serveur génère le code HTML complet d'une page sur le serveur en réponse à la navigation. Cela évite les allers-retours supplémentaires pour la récupération des données et la création de modèles côté client, car le moteur de rendu les gère avant que le navigateur ne reçoive une réponse.
Le rendu côté serveur produit généralement un FCP rapide. L'exécution de la logique et du rendu des pages sur le serveur vous permet d'éviter d'envoyer beaucoup de code JavaScript au client. Cela permet de réduire le TTBT 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.
Cela a du sens, car avec le rendu côté serveur, vous n'envoyez en réalité que du texte et des liens au navigateur de l'utilisateur. Cette approche peut bien fonctionner pour diverses conditions d'appareil et de réseau, et ouvre des optimisations de navigateur intéressantes, telles que l'analyse de documents en streaming.
 
  Avec le rendu côté serveur, les utilisateurs sont moins susceptibles d'être laissés en attente de l'exécution de JavaScript lié au processeur avant de pouvoir utiliser votre site. Même lorsque vous ne pouvez pas éviter le code JavaScript tiers, l'utilisation du rendu côté serveur pour réduire vos propres coûts JavaScript peut vous donner plus de budget pour le reste. Toutefois, cette approche présente un inconvénient potentiel : la génération de pages sur le serveur prend du temps, ce qui peut augmenter le TTFB de votre page.
La question de savoir si le rendu côté serveur est suffisant pour votre application dépend en grande partie du type d'expérience que vous créez. Il existe un débat de longue date sur les applications correctes du rendu côté serveur par rapport au rendu côté client, mais vous pouvez toujours choisir d'utiliser le rendu côté serveur pour certaines pages et pas pour d'autres. Certains sites ont adopté avec succès des techniques de rendu hybride. Par exemple, Netflix effectue le rendu côté serveur de ses pages de destination relativement statiques, tout en prefetching le code JavaScript pour les pages nécessitant de nombreuses interactions. Cela permet à ces pages plus lourdes, dont le rendu est effectué côté client, d'avoir plus de chances de se charger rapidement.
Avec de nombreux frameworks, bibliothèques et architectures modernes, vous pouvez afficher la même application sur le client et le serveur. Vous pouvez utiliser ces techniques pour le rendu côté serveur. Toutefois, les architectures où le rendu se produit à la fois sur le serveur et sur le client constituent une classe de solution à part entière, 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 les solutions basées sur celles-ci, comme Next.js, pour le rendu côté serveur. Les utilisateurs de Vue peuvent consulter le guide de rendu côté serveur de Vue ou utiliser Nuxt. Angular dispose d'Universal.
Cependant, la plupart des solutions populaires utilisent une forme d'hydratation. Vous devez donc connaître les approches utilisées par votre outil.
Rendu statique
Le rendu statique a lieu au moment de la compilation. Cette approche offre un FCP rapide, ainsi qu'un TBT et un INP plus faibles, à condition de limiter la quantité de JavaScript côté client sur vos pages. Contrairement au rendu côté serveur, il permet également d'obtenir un TTFB rapide et constant, car le code HTML d'une page n'a pas besoin d'être généré de manière dynamique sur le serveur. En général, le rendu statique consiste à produire à l'avance un fichier HTML distinct pour chaque URL. Grâce aux réponses HTML générées à l'avance, vous pouvez déployer des rendus statiques sur plusieurs CDN pour profiter de la mise en cache en périphérie.
 
  Il existe de nombreuses solutions de rendu statique. Des outils tels que Gatsby sont conçus pour donner aux développeurs l'impression que leur application est rendue de manière dynamique, et non générée en tant qu'étape de compilation. Les outils de génération de sites statiques tels que 11ty, Jekyll et Metalsmith adoptent leur nature statique, en fournissant une approche davantage axée sur les modèles.
L'un des inconvénients du rendu statique est qu'il doit générer des fichiers HTML individuels pour chaque URL possible. Cela peut être difficile, voire impossible, lorsque vous devez prédire ces URL à l'avance et pour les sites comportant un grand nombre de pages uniques.
Les utilisateurs de React connaissent peut-être Gatsby, Next.js static export ou Navi, qui permettent tous de créer facilement des pages à partir de composants. Toutefois, le rendu statique et le prérendu se comportent différemment : les pages rendues de manière statique sont interactives sans avoir besoin d'exécuter beaucoup de 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 réellement interactives.
Si vous n'êtes pas sûr qu'une solution donnée soit un rendu statique ou un prérendu, essayez de désactiver JavaScript et de charger 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 encore comporter des fonctionnalités de base, comme des liens avec JavaScript désactivé, mais la majeure partie de la page est inactive.
Un autre test utile consiste à utiliser la limitation du réseau dans Chrome DevTools et à voir la quantité de JavaScript téléchargée avant qu'une page ne devienne interactive. Le prérendu nécessite généralement plus de code JavaScript pour devenir interactif. Ce code JavaScript est souvent plus complexe que l'approche d'amélioration progressive utilisée dans le rendu 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 surcharge de calcul importants. De nombreuses solutions de rendu côté serveur ne vident pas le cache de manière anticipée, retardent le TTFB ou doublent 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 monothread.
Les nouvelles API DOM du serveur React sont compatibles avec le streaming, ce qui permet d'envoyer la partie initiale d'une réponse HTML au navigateur plus rapidement, tandis que le reste est encore en cours de génération sur le serveur.
Pour "bien" effectuer le rendu côté serveur, il peut être nécessaire de trouver ou de créer une solution pour la mise en cache des composants, de gérer la consommation de mémoire, d'utiliser des techniques de mémoïsation, etc. Vous traitez ou reconstruisez souvent la même application deux fois, une fois sur le client et une fois sur le serveur. Le rendu côté serveur qui affiche le contenu plus tôt ne vous donne pas nécessairement moins de travail. Si vous avez beaucoup de travail à faire côté 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 des valeurs TBT et INP plus élevées pour votre site Web.
Le rendu côté serveur génère du code HTML à la demande pour chaque URL, mais il peut être plus lent que la simple diffusion de contenu statique. Si vous pouvez fournir un effort supplémentaire, le rendu côté serveur associé à la mise en cache HTML peut réduire considérablement le temps de rendu du serveur. L'avantage du rendu côté serveur est la possibilité d'extraire davantage de données "en direct" et de répondre à un ensemble de requêtes plus complet que ce qui est possible avec le rendu statique. Les pages qui nécessitent une personnalisation sont un exemple concret du type de requête qui ne fonctionne pas bien avec le rendu 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 service worker en pleine page ou le rendu côté serveur de contenus individuels ?
Affichage côté client
Le rendu côté client signifie que les pages sont affichées directement dans le navigateur avec JavaScript. Toutes les logiques, l'extraction de données, la création de modèles et le routage sont gérés sur le client plutôt que sur le serveur. Le résultat effectif est que davantage de données sont transmises à l'appareil de l'utilisateur depuis le serveur, ce qui présente ses propres compromis.
Il peut être difficile de créer et de maintenir un rendu côté client rapide pour les appareils mobiles.
En vous efforçant de respecter un budget JavaScript strict et de fournir de la valeur en un minimum d'aller-retours, vous pouvez faire en sorte que le rendu côté client reproduise presque les performances du rendu côté serveur pur. Vous pouvez accélérer le fonctionnement du parseur en fournissant des scripts et des données essentiels à l'aide de <link rel=preload>. Nous vous recommandons également d'envisager d'utiliser des modèles tels que PRPL pour que les navigations initiales et ultérieures semblent instantanées.
 
  Le principal inconvénient du rendu côté client est que la quantité de JavaScript requise a tendance à augmenter à mesure qu'une 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 se font 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 le rendu côté client et s'appuient sur de grands bundles JavaScript doivent envisager le fractionnement agressif du code pour réduire le TBT et l'INP lors du chargement de la page, ainsi que le chargement différé de JavaScript pour ne servir que ce dont l'utilisateur a besoin, au moment où il en a besoin. Pour les expériences peu ou pas interactives, 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ées par la plupart des pages vous permet d'appliquer la technique de mise en cache du shell d'application. Combiné aux service workers, cela peut améliorer considérablement les performances perçues lors des visites répétées, car la page peut charger très rapidement le code HTML et les dépendances de son application shell à partir de CacheStorage.
L'hydratation combine le rendu côté serveur et côté client
L'hydratation est une approche qui atténue les compromis entre le rendu côté client et côté serveur en effectuant les deux. Les requêtes de navigation, comme les chargements ou rechargements de pages entières, sont traitées par un serveur qui affiche l'application en HTML. Le code JavaScript et les données utilisées pour le rendu sont ensuite intégrés au document obtenu. Lorsqu'il est effectué avec soin, ce processus permet d'obtenir un FCP rapide comme le rendu côté serveur, puis de "reprendre" en effectuant un nouveau rendu côté client.
Il s'agit d'une solution efficace, mais elle peut avoir 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 important sur le TBT et l'INP, même s'il améliore le FCP. Les pages rendues côté serveur peuvent sembler chargées et interactives, mais elles ne peuvent pas réellement répondre aux entrées tant que les scripts côté client pour les composants ne sont pas 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 perturber et frustrer l'utilisateur.
Un problème de réhydratation : une application pour le prix de deux
Pour que le JavaScript côté client puisse prendre le relais avec précision là où le serveur s'est arrêté, sans avoir à demander à nouveau toutes les données avec lesquelles le serveur a rendu son HTML, la plupart des solutions de rendu côté serveur sérialisent la réponse à partir des dépendances de données d'une UI en tant que balises de script dans le document. Comme cela duplique beaucoup de code HTML, l'hydratation peut entraîner d'autres problèmes que le simple retard d'interactivité.
 
  Le serveur renvoie une description de l'UI 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'UI qui démarre ensuite sur le client. L'UI ne devient interactive qu'une fois que bundle.js a fini de se charger et de s'exécuter.
Les métriques de performances collectées à partir de sites Web réels utilisant le rendu côté serveur et la réhydratation indiquent que ce n'est que rarement 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.
 
  Il existe une solution pour le rendu côté serveur avec réhydratation. À court terme, le fait d'utiliser le rendu côté serveur uniquement pour les contenus hautement cachables peut réduire le TTFB, ce qui produit des résultats similaires au prérendu. La réhydratation incrémentielle, progressive ou partielle pourrait être la clé pour rendre cette technique plus viable à l'avenir.
Afficher en flux continu le rendu côté serveur et réhydrater progressivement
Le rendu côté serveur a connu plusieurs évolutions au cours des dernières années.
Le rendu côté serveur en flux continu vous permet d'envoyer du code HTML par blocs que le navigateur peut afficher progressivement à mesure qu'il les reçoit. Cela permet de fournir le balisage plus rapidement à vos utilisateurs, ce qui accélère le FCP. Dans React, les flux étant asynchrones dans renderToPipeableStream(), par rapport à renderToString() synchrone, la contre-pression est bien gérée.
La réhydratation progressive est également à envisager (React l'a implémentée). Avec cette approche, les éléments individuels d'une application rendue côté serveur sont "démarrés" 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 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 des parties de la page à faible priorité pour éviter qu'elle ne bloque le thread principal, ce qui permet aux interactions utilisateur de se produire plus tôt après que l'utilisateur les a initiées.
La réhydratation progressive peut également vous aider à éviter l'un des pièges les plus courants de la réhydratation du rendu côté serveur : un arbre DOM rendu côté serveur est détruit, puis immédiatement reconstruit, le plus souvent parce que le rendu initial synchrone côté client nécessitait des données qui n'étaient pas tout à fait prêtes, souvent un Promise qui n'a pas encore été résolu.
Réhydratation partielle
La réhydratation partielle s'est avérée difficile à mettre en œuvre. Cette approche est une extension de la réhydratation progressive qui analyse les différentes parties de la page (composants, vues ou arborescences) et identifie celles qui présentent peu ou pas d'interactivité ni de réactivité. Pour chacune de ces parties principalement statiques, le code JavaScript correspondant est ensuite transformé en références inertes et en éléments décoratifs, ce qui réduit leur empreinte côté client à presque zéro.
L'approche de ré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 rendu par le serveur pour les parties inertes de l'application est disponible sans un chargement complet de la page.
Rendu trisomorphe
Si les service workers sont une option pour vous, envisagez le rendu trisomorphe. Cette technique vous permet d'utiliser le rendu côté serveur en streaming pour les navigations initiales ou non JavaScript, puis de laisser votre service worker prendre en charge le rendu du code HTML pour les navigations après son installation. Cela peut permettre de maintenir à jour les composants et les modèles mis en cache, 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 routage et de création de modèles entre le serveur, la page client et le service worker.
 
  Considérations liées au SEO
Lorsqu'elles choisissent une stratégie de rendu Web, les équipes tiennent souvent compte de l'impact sur le 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 rendu est souvent limité. Le rendu côté client peut fonctionner, mais nécessite souvent des tests et une surcharge supplémentaires. Plus récemment, l'affichage dynamique est également devenu une option à envisager si votre architecture dépend fortement du 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 choisie produit l'effet escompté. Il affiche un aperçu visuel de l'apparence de n'importe quelle page pour le robot d'exploration de Google, le contenu HTML sérialisé qu'il trouve après l'exécution de JavaScript et les erreurs rencontrées lors du rendu.
 
  Conclusion
Lorsque vous choisissez une approche de rendu, mesurez et comprenez vos goulots d'étranglement. Déterminez si l'affichage statique ou l'affichage côté serveur peuvent vous aider à atteindre votre objectif. Il est tout à fait possible de fournir principalement du code HTML avec un minimum de code JavaScript pour rendre une expérience interactive. Voici une infographie pratique illustrant le spectre client-serveur :
 
  Crédits {:#credits}
Merci à tous pour vos avis et votre inspiration :
Jeffrey Posnick, Houssein Djirdeh, Shubhie Panicker, Chris Harrelson et Sebastian Markbåge.
 
 
        
        