L'une des décisions fondamentales que les développeurs Web doivent prendre est de savoir 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 auprès de grands sites au cours des dernières années. De manière générale, nous encourageons les développeurs à opter pour 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 faisons notre choix lorsque nous prenons cette décision, nous devons avoir une compréhension solide de chaque approche et une terminologie cohérente à utiliser lorsque nous en parlons. Les différences entre les approches de rendu aident à illustrer les compromis du rendu sur le Web du point de vue des performances des pages.
Terminologie
Commençons par définir la terminologie que nous utiliserons.
Affichage
- Rendu côté serveur (SSR)
- Affichage d'une application sur le serveur pour envoyer du code HTML au client plutôt que du code JavaScript.
- Rendu côté client (CSR)
- Affichage d'une application dans un navigateur, à l'aide de JavaScript pour modifier le DOM.
- Réhydratation
- "Démarrage" des vues JavaScript sur le client afin qu'elles réutilisent l'arborescence DOM et les données du code HTML généré par le serveur.
- Prérendu
- Exécution d'une application côté client au moment de la compilation pour capturer son état initial en tant que code HTML statique.
Performances
- Délai avant le premier octet (TTFB)
- Temps écoulé 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 proxy pour l'INP qui calcule la durée pendant laquelle le thread principal a été bloqué lors du chargement de la page.
Rendu 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 l'extraction et la création de modèles de données sur le client, car le moteur de rendu les gère avant que le navigateur ne reçoive une réponse.
L'affichage côté serveur produit généralement un FCP rapide. Exécuter la logique de la page et le rendu sur le serveur vous permet d'éviter d'envoyer beaucoup de code JavaScript au client. Cela permet de réduire le TBT d'une page, ce qui peut également entraîner une diminution 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 d'être exécutées plus tôt. Cela est logique, 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 fonctionner pour diverses conditions d'appareil et de réseau, et ouvre des optimisations de navigateur intéressantes, comme l'analyse de documents en streaming.
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 utiliser votre site. Même si vous ne pouvez pas éviter le code JavaScript tiers, utiliser le rendu côté serveur pour réduire vos propres coûts JavaScript first party 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.
Le rendu côté serveur est-il suffisant pour votre application ? Cela dépend en grande partie du type d'expérience que vous créez. Un débat de longue date porte sur les applications appropriées 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 non pour d'autres. Certains sites ont adopté avec succès des techniques de rendu hybride. Par exemple, Netflix effectue le rendu de ses pages de destination relativement statiques sur le serveur, tout en prefetching le code JavaScript pour les pages à forte interaction, ce qui permet à ces pages plus lourdes rendues côté client de se charger plus rapidement.
De nombreux frameworks, bibliothèques et architectures modernes vous permettent d'afficher la même application à la fois 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 solutions distincte, avec des caractéristiques et des compromis de performances très différents. Les utilisateurs de React peuvent utiliser des API DOM côté serveur ou des solutions basées sur celles-ci, comme Next.js, pour le rendu côté serveur. Les utilisateurs de Vue peuvent utiliser le guide de rendu côté serveur de Vue ou Nuxt. Angular propose Universal. Toutefois, la plupart des solutions populaires utilisent une forme d'hydratation. Tenez donc compte des approches utilisées par votre outil.
Rendu statique
Le rendu statique se produit 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 code JavaScript côté client sur vos pages. Contrairement au rendu côté serveur, il permet également d'obtenir un TTFB toujours rapide, car le code HTML d'une page n'a pas besoin d'être généré dynamiquement sur le serveur. En général, le rendu statique consiste à produire un fichier HTML distinct pour chaque URL à l'avance. Avec des 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.
Les solutions de 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 en tant qu'étape de compilation. Les outils de génération de sites statiques tels que 11ty, Jekyll et Metalsmith s'appuient sur leur nature statique et offrent une approche plus 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 s'avérer difficile, voire impossible, lorsque vous ne pouvez pas prédire à l'avance quelles seront ces URL ou pour les sites comportant un grand nombre de pages uniques.
Les utilisateurs de React peuvent être familiarisés avec Gatsby, l'exportation statique Next.js 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 à 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 utilise le rendu statique ou le 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 toujours comporter certaines 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 le bloquant de débit réseau dans Chrome DevTools et à voir combien de téléchargements JavaScript sont effectués avant qu'une page ne devienne interactive. Le prérendu nécessite généralement plus de code JavaScript pour devenir interactif, et ce code JavaScript tend à être plus complexe que l'approche d'amélioration progressive utilisée dans le rendu statique.
Rendu côté serveur par rapport au 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 n'effectuent pas de vidage anticipé, ne retardent pas le TTFB ni ne 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 à un seul thread.
Les nouvelles API DOM de serveur React sont compatibles avec le streaming, ce qui permet d'envoyer plus rapidement la partie initiale d'une réponse HTML au navigateur pendant que le reste est encore généré sur le serveur.
Pour obtenir un rendu côté serveur "correct", vous devrez peut-être trouver ou créer une solution pour le mise en cache des composants, gérer la consommation de mémoire, utiliser des techniques de mémorisation et d'autres problèmes. 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 permet pas nécessairement de faire moins de travail. Si vous avez beaucoup de travail à effectuer sur le client après qu'une réponse HTML générée par le serveur est arrivée sur le client, cela peut toujours entraîner une augmentation du TBT et de l'INP de votre site Web.
Le rendu côté serveur produit du code HTML à la demande pour chaque URL, mais il peut être plus lent que la simple diffusion de contenu rendu statique. Si vous pouvez effectuer les tâches supplémentaires, le rendu côté serveur et le mise en cache HTML peuvent réduire considérablement le temps de rendu du serveur. L'avantage du rendu côté serveur est qu'il permet d'extraire plus 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 nécessitant 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 le cache de service worker sur toute la page ou de simplement effectuer le rendu côté serveur de composants de contenu individuels ?
Rendu côté client
L'affichage côté client consiste à afficher les pages directement dans le navigateur avec JavaScript. Toute la logique, l'extraction des données, la création de modèles et le routage sont gérés sur le client plutôt que sur le serveur. En conséquence, plus de données sont transmises au serveur de l'appareil de l'utilisateur, ce qui entraîne son propre ensemble de compromis.
Il peut être difficile de rendre le rendu côté client rapide et de le maintenir pour les appareils mobiles.
Avec un peu d'effort pour maintenir un budget JavaScript strict et générer de la valeur en aussi peu de allers-retours que possible, vous pouvez obtenir un rendu côté client qui reproduit presque les performances du rendu côté serveur pur. Vous pouvez accélérer le travail de l'analyseur en fournissant des scripts et des données critiques à 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 semblent instantanées.
Le principal inconvénient du rendu côté client est que la quantité de code 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 se disputent 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 d'utiliser le fractionnement de code agressif pour réduire le TBT et l'INP lors du chargement de la page, ainsi que le préchargement JavaScript pour ne diffuser que ce dont l'utilisateur a besoin, quand il en a besoin. 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 développeurs d'applications monopages, identifier les parties principales de l'interface utilisateur partagées par la plupart des pages vous permet d'appliquer la technique de mise en cache de la coque d'application. Combiné à des services workers, cela peut considérablement améliorer les performances perçues lors des visites répétées, car la page peut charger très rapidement son shell d'application HTML et ses dépendances à partir de CacheStorage
.
La réhydratation combine le rendu côté serveur et côté client
La réhydratation est une approche qui tente de gommer les compromis entre le rendu côté client et le rendu côté serveur en effectuant les deux. Les requêtes de navigation telles que les chargements ou les rechargements de pages complètes sont gérées par un serveur qui génère l'application en HTML. Le code JavaScript et les données utilisées pour l'affichage sont ensuite intégrés au document généré. Si vous procédez avec précaution, vous obtiendrez un FCP rapide comme le rendu côté serveur, puis vous "reprendez" en effectuant un nouveau rendu côté client. Il s'agit d'une solution efficace, mais elle peut présenter des inconvénients considérables en termes de performances.
L'inconvénient principal 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 générées 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 prêter à confusion et être frustrant pour l'utilisateur.
Problème de réhydratation: une application pour le prix de deux
Pour que le code JavaScript côté client puisse "reprendre" précisément là où le serveur s'était arrêté, sans demander à nouveau toutes les données avec lesquelles le serveur a généré 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 UI en tant que balises de script dans le document. Comme cela duplique beaucoup de code HTML, la réhydratation peut entraîner d'autres problèmes que l'interactivité retardée.
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'après le chargement et l'exécution de bundle.js
.
Les métriques de performances collectées sur des sites Web réels utilisant le rendu et la réhydratation côté serveur indiquent que ce n'est rarement la meilleure option. La raison la plus importante est son impact sur l'expérience utilisateur, lorsqu'une page semble prête, mais qu'aucune de ses fonctionnalités interactives ne fonctionne.
Il existe cependant un espoir pour le rendu côté serveur avec réhydratation. À court terme, n'utiliser que le rendu côté serveur pour les contenus hautement mis en cache peut réduire le délai avant première réponse, 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 l'affichage côté serveur en streaming et réhydrater progressivement
Le rendu côté serveur a connu un certain nombre d'évolutions au cours des dernières années.
Le rendu côté serveur en streaming vous permet d'envoyer du code HTML par blocs que le navigateur peut progressivement afficher à mesure qu'il le reçoit. Cela permet de diffuser le balisage plus rapidement auprès de vos utilisateurs, ce qui accélère votre FCP. Dans React, les flux étant asynchrones dans renderToPipeableStream()
, par rapport à renderToString()
synchrone, cela signifie que la contre-pression est bien gérée.
La réhydratation progressive est également à envisager, et React l'a implémentée. Avec cette approche, les éléments individuels d'une application rendue par le serveur sont "démarrés" au fil du temps, au lieu de l'approche courante consistant à initialiser l'ensemble de l'application en une seule fois. Cela peut aider à réduire la quantité de code 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'elles ne bloquent le thread principal, ce qui permet aux interactions utilisateur de se produire plus rapidement après leur initiation.
La réhydratation progressive peut également vous aider à éviter l'un des écueils les plus courants de la réhydratation du rendu côté serveur: un arbre DOM rendu côté serveur est détruit, puis reconstruit immédiatement, le plus souvent parce que le rendu côté client synchrone initial 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 éléments individuels de la page (composants, vues ou arborescences) et identifie les éléments peu interactifs ou non réactifs. Pour chacune de ces parties principalement statiques, le code JavaScript correspondant est ensuite transformé en références inertes et en fonctionnalités décoratives, ce qui réduit leur empreinte côté client à presque zéro.
L'approche d'hydratation partielle présente ses propres problèmes et compromis. Cela pose des défis intéressants pour le cache, et la navigation côté client signifie que nous ne pouvons pas supposer que le code HTML généré côté serveur pour les parties inertes de l'application est disponible sans un chargement complet de la page.
Rendu trismorphique
Si les service workers sont une option pour vous, envisagez le rendu trismorphique. Il s'agit d'une technique qui vous permet d'utiliser le rendu côté serveur en streaming pour les navigations initiales ou non JavaScript, puis de demander à votre service worker de prendre en charge le rendu HTML pour les navigations après son installation. Cela permet de mettre à jour les composants et les modèles mis en cache, et d'activer les navigations de style 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 client et le service worker.
Considérations concernant le SEO
Lorsque les équipes choisissent une stratégie de rendu Web, elles prennent souvent en compte l'impact du référencement 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 des frais supplémentaires. Plus récemment, l'affichage dynamique est également devenu une option à envisager 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 fonctionne comme vous l'espérez. 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 identifiez vos goulots d'étranglement. Demandez-vous si l'affichage statique ou l'affichage côté serveur peut vous aider à atteindre la plupart de vos objectifs. Il est acceptable d'utiliser principalement du code HTML avec un minimum de code JavaScript pour obtenir une expérience interactive. Voici une infographie pratique illustrant le spectre serveur-client:
Crédits
Merci à tous pour vos avis et votre inspiration:
Jeffrey Posnick, Houssein Djirdeh, Shubhie Panicker, Chris Harrelson et Sebastian Markbåge