Le rendu côté serveur, ou SSR, est une expression que vous entendez souvent dans la communauté de développement frontend.
Au niveau le plus basique, le rendu côté serveur est exactement ce qu’il décrit : le rendu d’applications sur le serveur. Vous accédez à un site Web, il fait une demande au serveur, il rend du code HTML et vous récupérez le résultat entièrement rendu dans votre navigateur. Assez simple. Vous vous demandez peut-être pourquoi la communauté a même un mot à la mode pour cela.
Avant l’essor des applications Web riches et dynamiques qui reposaient fortement sur JavaScript et jQuery, toutes les applications Web étaient essentiellement rendues par un serveur. PHP, WordPress et même des sites HTML de base en sont des exemples.
Lorsque vous visitez une page sur l’un de ces sites, vous récupérez toutes les données HTML et tout. Si vous cliquez sur un lien, le navigateur fera une autre demande au serveur. Après la réponse, le navigateur actualisera et affichera la page suivante à partir de zéro. Cette approche fonctionne bien, et cela depuis des années; les navigateurs sont spectaculairement rapides pour rendre du HTML statique. Qu’est-ce qui a changé ?
Depuis le début du siècle, l’utilisation de JavaScript est passée d’une petite pincée ici et là pour l’interactivité des pages web à la langue de choix incontestée sur le web. Nous expédions constamment plus de logique et de JavaScript au navigateur.
Des frameworks d’une seule page comme React et Vue ont inauguré cette nouvelle ère d’applications Web dynamiques, complexes et basées sur les données. Ces stations de service diffèrent des applications rendues par le serveur car elles ne récupèrent pas le contenu entièrement rendu avec les données du serveur avant le rendu à l’écran.
Les applications rendues côté client rendent leur contenu dans le navigateur à l’aide de JavaScript. Plutôt que de récupérer tout le contenu du serveur, ils récupèrent simplement une page HTML barebones sans contenu corporel et restituent tout le contenu à l’intérieur en utilisant JavaScript.
L’avantage de ceci est que vous évitez les actualisations de page complètes qui se produisent avec des applications entièrement rendues par le serveur, ce qui peut être un peu gênant pour l’utilisateur. Les applications rendues par le client d’une seule page mettront à jour le contenu sur votre écran, récupéreront les données des API et se mettront à jour juste devant vous sans aucune actualisation de page. Cette caractéristique est ce qui rend les applications Web modernes accrocheuses et « natives » lorsque vous interagissez avec elles.
- Compromis de rendu côté client
- SEO
- Performances de charge initiale
- Le meilleur des deux mondes
- Tutoriel: L’implémentation de SSR
- Nos outils
- Diagramme d’architecture
- Création de notre application
- Configuration du projet
- Rendu de l’application en Lambda
- Hébergement et touches finales
- Exécution de notre application
- Où aller à partir d’ici
- Concurrence provisionnée
- Versions Lambda
- Application Web Full-stack
- Conclusion
- Visibilité totale sur les applications React de production
Compromis de rendu côté client
Ce n’est pas tout le soleil et les arcs-en-ciel dans le monde des SPAS rendus côté client. Il y a quelques compromis qui viennent avec le rendu de votre application côté client. Deux exemples principaux sont le référencement et les performances de charge initiale.
SEO
Étant donné que les applications rendues par le client renvoient une page HTML nue avec très peu de contenu avant que JavaScript n’entre en jeu et ne restitue le reste, il peut être difficile pour les robots d’exploration des moteurs de recherche de comprendre la structure HTML de votre page, ce qui nuit aux classements de recherche de votre site. Google a fait beaucoup de bon travail autour de cela, mais il est toujours recommandé d’éviter le rendu côté client si le référencement est particulièrement important.
Performances de charge initiale
Avec les applications rendues par le client, les choses suivantes se produisent généralement lorsque vous ouvrez la page pour la première fois:
- L’application charge du code HTML de base, comme un shell d’application ou une barre de navigation statique
- Vous voyez un indicateur de chargement d’une sorte
- Votre contenu est ensuite rendu
Le problème avec cela est que votre application n’affichera rien tant que le JavaScript ne se chargera pas complètement du réseau et aura fini de rendre les éléments sur votre écran.
En un mot, le problème des performances côté client en général est que vous ne pouvez pas contrôler sur quel appareil client quelqu’un utilise votre application, qu’il s’agisse de son smartphone de pointe, de son puissant ordinateur de bureau haut de gamme ou de son smartphone bas de gamme à 100 $.
Nous contrôlons cependant le serveur. Nous pouvons presque toujours donner à notre serveur plus de CPU et de mémoire et le modifier pour le rendre plus performant pour nos utilisateurs.
Le meilleur des deux mondes
Nous pouvons avoir le meilleur des deux mondes lorsque nous utilisons le rendu côté serveur avec des technologies frontend modernes. La façon dont cela fonctionne généralement est que le serveur restitue et renvoie l’application entièrement rendue lors du premier chargement. L’étape suivante, connue sous le nom d’hydratation, est celle où votre bundle JavaScript sera téléchargé et exécuté. Cela attache des gestionnaires d’événements et des câbles comme votre routeur côté client.
Avec cette approche, vous obtenez tous les avantages de SSR sur la charge initiale, puis chaque interaction à partir de ce moment sera gérée par JavaScript côté client. Cela permet une charge initiale rapide et conviviale pour le référencement, suivie de l’expérience d’application Web dynamique sur une seule page que nous connaissons et aimons.
Des applications comme celle-ci sont appelées applications universelles car le même JavaScript s’exécute sur le client et le serveur. Vous pouvez également entendre le terme plus chic « isomorphe » utilisé, ce qui signifie exactement la même chose.
Tutoriel: L’implémentation de SSR
SSR n’est pas non plus sans compromis. Cela ajoute de la surcharge à votre développement en introduisant une configuration plus complexe ainsi qu’en devant héberger et gérer votre propre serveur. Ces problèmes sont la raison pour laquelle des cadres incroyables comme Next.js et Razzle sont très populaires: ils abstiennent la partie de configuration SSR et vous permettent de vous concentrer sur l’écriture du code de l’interface utilisateur.
Dans ce tutoriel, nous n’utiliserons aucun framework SSR. La meilleure façon d’apprendre comment quelque chose fonctionne est de le construire, nous allons donc apprendre à créer la configuration SSR la plus simple que nous puissions offrir:
- CDN global
- API backend entièrement fonctionnelle
- Aucun serveur ou infrastructure à gérer
- Déploiement à commande unique
Nous allons déployer une application React universelle rendue par le serveur créée avec create-react-app sur Amazon Web Services (AWS). Vous n’avez pas besoin d’avoir de l’expérience avec AWS pour suivre.
Nos outils
Afin de construire notre application, nous allons utiliser quelques services AWS différents.
- AWS Amplify: Un framework de haut niveau pour la gestion des services AWS, principalement pour le développement mobile et web
- AWS Lambda : Exécutez du code dans le cloud sans gérer de serveurs
- AWS Cloudfront (CDN) : Un réseau de diffusion de contenu responsable de la diffusion et de la mise en cache du contenu partout dans le monde
- AWS Simple Storage Service (S3) : Où nous stockerons nos actifs statiques (JS, CSS, etc.)
Diagramme d’architecture
Notre fonction Lambda est responsable du rendu serveur de notre application React. Nous utiliserons S3 pour stocker notre contenu statique et Cloudfront CDN pour le servir. Vous n’avez pas besoin d’avoir une connaissance préalable de ces services, car AWS Amplify nous permettra de les créer très simplement.
Création de notre application
Tout d’abord, vous devez installer l’interface de ligne de commande AWS Amplify et créer un compte AWS si vous n’en avez pas déjà un. Vous pouvez le faire en suivant ce petit guide.
Configuration du projet
Maintenant qu’Amplify est configuré, nous pouvons commencer à configurer notre projet React. Nous allons utiliser la fantastique application create-react pour nous aider. En supposant que vous avez un nœud.js et npm installés, nous pouvons exécuter:
npx create-react-app amplify-ssrcd amplify-ssr yarn add aws-amplify amplify init
Sélectionnez les options par défaut dans l’assistant AWS Amplify.
Notre projet React est maintenant démarré avec Amplify et prêt à nous permettre d’ajouter notre « serveur » pour SSR. Nous le faisons en exécutant amplify add api
et en répondant à quelques questions:
$ amplify add api? Please select from one of the below mentioned services: REST? Provide a friendly name for your resource to be used as a label for this category in the project: amplifyssr? Provide a path (e.g., /items): /ssr? Choose a Lambda source: Create a new Lambda function? Provide a friendly name for your resource to be used as a label for this category in the project: amplifyssr? Provide the AWS Lambda function name: ssr? Choose the function runtime that you want to use: NodeJS? Choose the function template that you want to use: Serverless expressJS function? Do you want to access other resources created in this project from your Lambda function? N? Do you want to edit the local lambda function now? N? Restrict API access: N? Do you want to add another path? N
Cela créera les modèles, répertoires et codes pertinents nécessaires à notre infrastructure AWS et à notre backend : une fonction AWS Lambda qui exécutera un petit serveur Express responsable du rendu de notre application React.
Avant de déployer notre infrastructure, nous devons apporter certaines modifications à l’intérieur de notre application React pour la préparer au rendu côté serveur. Ouvrez src/App.js
(le composant d’application principal de votre application React) et collez les éléments suivants:
import React from 'react';function App() { return ( <div className="App"> <header className="App-header"> Server Rendered React App </header> </div> );}export default App;
Ensuite, nous devons créer un script qui rendra notre application React côté serveur. Ceci est fait avec la fonction renderToString
dans le package react-dom/server
. Cette fonction est responsable de la prise de notre composant <App />
et de son rendu côté serveur sous forme de chaîne, prête à être renvoyée en HTML entièrement rendu au client.
Créez un fichier à src/render.js
avec le code suivant:
import React from "react";import { renderToString } from "react-dom/server";import App from "./App";export default () => renderToString(<App />);
Génial — notre application React côté client a tout le code dont elle a besoin pour être rendue côté serveur. Cela signifie que nous devons maintenant coder le point de terminaison côté serveur qui rendra notre application React.
Nous avons cependant un problème — nous avons besoin de la fonction src/render
et de notre code de composant <App />
pour être exécutés côté serveur. Le serveur ne sait rien des modules React ou même ES par défaut. Pour cette raison, nous allons transpiler le code de l’application React en utilisant Babel côté serveur.
Pour ce faire, installons quelques dépendances Babel dans notre projet.
yarn add --dev @babel/core @babel/cli @babel/preset-react @babel/preset-env
Ensuite, créez un .babelrc
à la racine de votre projet. Ce fichier est utilisé pour configurer Babel et lui indiquer quels plugins/préréglages utiliser.
{ "presets":}
Enfin, mettons à jour notre package.json
pour transpiler notre code dans le cadre de l’étape de construction. Cela transpilera les fichiers dans le répertoire amplify/backend/function/amplifyssr/src/client
, où nous stockerons tout le JavaScript universel qui doit être exécuté côté client ainsi que le serveur pour SSR.
"scripts": { "start": "react-scripts start", "transpile": "babel src --out-dir amplify/backend/function/amplifyssr/src/client --copy-files", "build": "npm run transpile && react-scripts build && npm run copy", "copy": "cp build/index.html amplify/backend/function/amplifyssr/src/client", "test": "react-scripts test", "eject": "react-scripts eject" },
Rendu de l’application en Lambda
La configuration de construction est terminée! Passons à amplify/backend/function/amplifyssr/src
et installons react
et react-dom
, car ils seront tous deux nécessaires pour que le Lambda exécute SSR.
yarn add react react-dom
Maintenant, configurez notre serveur Express, qui fonctionnera sur Lambda. La fonction Lambda a été générée automatiquement pour nous lorsque nous avons terminé l’étape amplify add api
plus tôt et choisi une API REST
et ExpressJS
.
Amplify a déjà configuré le serveur Express pour que nous l’exécutions sur Lambda, il ne nous reste donc plus qu’à ajouter un point de terminaison au serveur – rendre notre application React lorsque quelqu’un atteint l’URL de l’API dans le navigateur. Mettez à jour votre fichier amplify/backend/function/amplifyssr/src/app.js
pour qu’il contienne le code suivant:
/* Amplify Params - DO NOT EDIT ENV REGIONAmplify Params - DO NOT EDIT */const express = require('express')const bodyParser = require('body-parser')const awsServerlessExpressMiddleware = require('aws-serverless-express/middleware')const fs = require('fs');const render = require('./client/render').default;// declare a new express appconst app = express()app.use(bodyParser.json())app.use(awsServerlessExpressMiddleware.eventContext())// Enable CORS for all methodsapp.use(function(req, res, next) { res.header("Access-Control-Allow-Origin", "*") res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept") next()});app.get('*', function(req, res) { // Read the index.html file from the create-react-app build const html = fs.readFileSync("./client/index.html", "utf-8"); // Server side render the react application const markup = render(); // Replace the empty body of index.html with the fully server rendered react application and send it back to the client res.send(html.replace(`<div></div>`, `<div>${markup}</div>`))});module.exports = app
Notre serveur Express est maintenant compatible SSR et nous pouvons déployer notre application React.
Hébergement et touches finales
Une fois que nous aurons reçu le HTML rendu par le serveur à partir du rendu initial de notre application, nous récupérerons ensuite le bundle JavaScript côté client pour prendre le relais et nous donner un SPA entièrement interactif.
Nous avons besoin d’un endroit pour héberger nos fichiers JavaScript et statiques côté client. Dans AWS, le service généralement utilisé pour cela est S3 (Simple Storage Service), un magasin d’objets cloud massivement évolutif.
Nous allons également mettre un CDN devant lui pour une mise en cache et des performances globales. Avec Amplify, nous pouvons créer ces deux ressources pour notre projet en exécutant quelques commandes à partir de notre répertoire racine de projet:
$ amplify add hostingSelect the plugin module to execute Amazon CloudFront and S3? Select the environment setup: PROD (S3 with CloudFront using HTTPS)? hosting bucket name (name your bucket or use the default)
Vous pouvez désormais déployer l’ensemble de votre infrastructure, y compris votre fonction Lambda Express server, votre compartiment S3 et votre CDN, en exécutant la commande amplify publish
.
La sortie de votre console affichera toutes les ressources pertinentes de vos modèles créés pour vous par Amplify. Veuillez noter que la création d’un CDN Cloudfront peut prendre un certain temps, alors soyez patient. Une fois vos ressources créées, votre URL CDN Cloudfront sera affichée dans le terminal.
Publish started for S3AndCloudFront✔ Uploaded files successfully.Your app is published successfully.https://d3gdcgc9a6lz30.cloudfront.net
La dernière chose que nous devons faire est de dire à React d’où récupérer notre bundle côté client une fois l’application rendue par le serveur. Cela se fait dans create-react-app en utilisant la variable d’environnement PUBLIC_URL
. Mettons à jour à nouveau nos scripts React app package.json
pour ressembler à ce qui suit:
"scripts": { "start": "react-scripts start", "transpile": "babel src --out-dir amplify/backend/function/amplifyssr/src/client --copy-files", "build": "npm run transpile && PUBLIC_URL=<your-cloudfront-url> react-scripts build && npm run copy", "copy": "cp build/index.html amplify/backend/function/amplifyssr/src/client", "test": "react-scripts test", "eject": "react-scripts eject" },
Reconstruisez et déployez votre application sur AWS avec cette configuration mise à jour.
amplify publish
Nous devrions maintenant avoir une application React entièrement rendue côté serveur fonctionnant sur AWS !
Exécution de notre application
L’URL de votre API SSR se trouve à amplify/backend/amplify-meta.json
. Recherchez le RootUrl
dans votre fichier JSON et vous devriez voir l’URL à laquelle vous pouvez visiter votre nouvelle application rendue par le serveur. Cela devrait ressembler à ce qui suit:
"output": { "ApiName": "amplifyssr", "RootUrl": "https://g6nfj3bvsg.execute-api.eu-west-1.amazonaws.com/dev", "ApiId": "g6nfj3bvsg"},
Visitez l’URL de la passerelle API dans votre navigateur à <your-api-url>/ssr
et vous devriez voir votre nouvelle application React rendue par le serveur ! Si vous plongez dans l’onglet Réseau de votre navigateur de choix et affichez les requêtes, vous remarquerez que la requête à /ssr
a une réponse HTML entièrement rendue avec notre application React rendue à l’intérieur du <body>
du document.
<div> <div class="App" data-reactroot=""> <header class="App-header">Server Rendered React App</header> </div></div>
Vous remarquerez également les demandes faites à votre URL Cloudfront depuis le navigateur pour charger le JavaScript côté client qui prendra en charge le rendu à partir d’ici, nous donnant le meilleur des mondes de rendu côté client et côté serveur.
Où aller à partir d’ici
Ce tutoriel est destiné à vous permettre de démarrer le rendu côté serveur le plus rapidement possible sans vous soucier de la gestion de l’infrastructure, des CDN, etc. Après avoir utilisé l’approche sans serveur, nous pouvons apporter quelques améliorations intéressantes à notre configuration.
Concurrence provisionnée
AWS Lambda peut rester extrêmement économique, car les fonctions Lambda qui n’ont pas été touchées depuis un certain temps resteront » inactives « . »Cela signifie essentiellement que lorsque nous les exécuterons à nouveau, il y aura ce que l’on appelle un « démarrage à froid » — un délai d’initialisation qui doit se produire avant que le Lambda ne réponde.
Après cela, le lambda est ensuite à nouveau « chaud » pendant un certain temps et répondra rapidement aux demandes ultérieures jusqu’à la longue période d’inactivité suivante. Cela peut entraîner des temps de réponse légèrement peu fiables.
Bien qu’il soit « sans serveur « , Lambda utilise des conteneurs légers pour traiter toutes les demandes. Chaque conteneur ne peut traiter qu’une seule demande à un moment donné. En plus du problème de démarrage à froid après une période d’inactivité, il en va de même lorsque de nombreuses requêtes simultanées atteignent la même fonction Lambda, provoquant un démarrage à froid de plus de conteneurs ou de travailleurs simultanés avant de répondre.
Dans le passé, beaucoup d’ingénieurs ont résolu ce problème en écrivant des scripts pour envoyer un ping au Lambda périodiquement pour le garder au chaud. Il existe désormais un moyen bien meilleur de résoudre ce problème natif AWS, et il est connu sous le nom de concurrence provisionnée.
Avec la simultanéité provisionnée, vous pouvez très facilement demander à un nombre donné de conteneurs dédiés de rester au chaud pour une fonction Lambda spécifique. Cela vous donnera un temps de réponse SSR beaucoup plus cohérent en période de charge élevée et sporadique.
Versions Lambda
Vous pouvez créer plusieurs versions Lambda pour vos fonctions et diviser le trafic entre elles. C’est très puissant dans notre application SSR car cela nous permet de faire des mises à jour du côté Lambda et de les tester A / B avec une plus petite partie des utilisateurs.
Vous pouvez publier plusieurs versions de votre Lambda et diviser le trafic entre elles en poids que vous spécifiez. Par exemple, vous pouvez rendre une bannière CTA sur un serveur pour certains utilisateurs afin de mesurer l’engagement, mais pas pour d’autres. Vous pouvez le faire avec des versions Lambda.
Application Web Full-stack
Comme expliqué précédemment, AWS Amplify crée déjà une API REST et un serveur Express pour nous, dans lesquels nous avons créé un point de terminaison pour rendre le serveur de notre application React. Nous pouvons toujours ajouter plus de code et de points de terminaison à ce serveur Express à amplify/backend/function/amplifyssr/src/app.js
, ce qui nous permet de transformer notre application en une application Web complète, complète avec base de données, authentification, etc.
Vous pouvez utiliser la fantastique suite d’outils AWS Amplify pour créer ces ressources ou vous connecter à votre propre infrastructure, même si elle n’est pas hébergée sur AWS. Vous pouvez traiter votre backend AWS Lambda comme n’importe quel autre serveur Express et le construire par-dessus.
Vous avez déjà configuré l’ensemble de votre pipeline de déploiement en exécutant amplify publish
afin que vous puissiez vous concentrer sur l’écriture de code. Le point de départ de ce tutoriel vous donne une flexibilité totale pour faire ce que vous voulez à partir d’ici.
Conclusion
Le rendu côté serveur ne doit pas nécessairement être difficile. Nous pouvons utiliser des outils entièrement gérés comme Next ou Razzle, qui sont étonnants en soi, mais pour beaucoup d’équipes, cela peut être un changement de paradigme beaucoup trop important compte tenu de leur code ou de leurs exigences existants. L’utilisation d’une approche personnalisée simple et nécessitant peu d’entretien peut vous faciliter la vie, en particulier si vous utilisez déjà AWS ou Amplify pour votre projet.
SSR peut ajouter une tonne de valeur à vos applications Web et fournir un boost de performance ou de référencement indispensable. Nous avons la chance dans la communauté du développement Web d’avoir des outils qui peuvent créer des CDN, des backends sans serveur et des applications Web entièrement hébergées en quelques commandes ou clics.
Même si vous ne pensez pas avoir besoin de SSR, c’est un sujet très répandu et courant dans l’écosystème JavaScript. Avoir une compréhension de ses avantages et de ses compromis sera utile à presque toute personne impliquée dans le domaine du développement Web.
J’espère que vous avez appris quelque chose aujourd’hui – merci d’avoir lu! N’hésitez pas à me contacter ou à me suivre sur Twitter, où je tweete et blogue sur JavaScript, Python, AWS, l’automatisation et le développement sans code.
Visibilité totale sur les applications React de production
Le débogage des applications React peut être difficile, en particulier lorsque les utilisateurs rencontrent des problèmes difficiles à reproduire. Si vous souhaitez surveiller et suivre l’état de Redux, faire apparaître automatiquement les erreurs JavaScript et suivre les requêtes réseau lentes et le temps de chargement des composants, essayez LogRocket.
LogRocket est comme un DVR pour les applications Web, enregistrant littéralement tout ce qui se passe sur votre application React. Au lieu de deviner pourquoi des problèmes surviennent, vous pouvez agréger et rendre compte de l’état dans lequel se trouvait votre application lorsqu’un problème s’est produit. LogRocket surveille également les performances de votre application, en établissant des rapports avec des mesures telles que la charge du processeur client, l’utilisation de la mémoire client, etc.
Le package middleware LogRocket Redux ajoute une couche supplémentaire de visibilité à vos sessions utilisateur. LogRocket enregistre toutes les actions et tous les états de vos magasins Redux.
Modernisez la façon dont vous déboguez vos applications React — commencez la surveillance gratuitement.