Qu'est-ce que le rendu côté serveur et en ai-je besoin?

Bonjour, Habr!



Dans la nouvelle année, commençons notre conversation avec vous avec un article de base sur le rendu côté serveur. Si vous êtes intéressé, une publication plus récente sur Nuxt.js et d'autres travaux de publication dans ce sens sont possibles.



Avec l'avènement des frameworks et bibliothèques JavaScript modernes, qui sont principalement destinés à la création de pages Web interactives et d'applications à page unique, l'ensemble du processus d'affichage des pages à l'utilisateur a beaucoup changé.



Avant l'avènement des applications entièrement générées par JS dans le navigateur, du HTML était servi au client en réponse à un appel HTTP. Cela pourrait être fait en renvoyant un fichier HTML statique avec le contenu, ou en traitant la réponse en utilisant un langage côté serveur (PHP, Python ou Java), et de manière plus dynamique.



Cette solution vous permet de créer des sites réactifs qui s'exécutent beaucoup plus rapidement que les sites standard de demande-réponse, puisqu'elle élimine le temps passé par la demande "en chemin".



Une réponse typique d'un serveur à une demande adressée à un site écrite en React ressemblerait à ceci:



<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <link rel="shortcut icon" href="/favicon.ico">
    <title>React App</title>
  </head>
  <body>
    <div id="root"></div>
    <script src="/app.js"></script>
  </body>
</html>
      
      





En sélectionnant cette réponse, notre navigateur sélectionnera également le "package" app.js



contenant notre application, et après une seconde ou deux, il affichera la page entière.



À ce stade, vous pouvez déjà utiliser l'inspecteur HTML intégré du navigateur pour afficher tout le HTML rendu. Cependant, en regardant le code source, nous ne verrons rien d'autre que le HTML ci-dessus.



Pourquoi c'est un problème?



Bien que ce comportement ne soit pas un problème pour la plupart de nos utilisateurs ou lors du développement d'une application, il peut devenir indésirable si:



  • -, ,
  • , ,


Si, d'un point de vue démographique, votre public cible appartient à l'un de ces groupes, alors travailler avec le site sera peu pratique - en particulier, les utilisateurs devront attendre longtemps, en regardant le signe "Chargement ..." (ou pire encore, sur un écran vide).



"D'accord, mais sur le plan démographique, mon public cible ne fait certainement pas partie de ces groupes, devrais-je donc m'inquiéter?"



Il y a deux autres choses à considérer lorsque vous travaillez avec une application rendue par le client: les moteurs de recherche et la présence sur les réseaux sociaux .



Aujourd'hui, de tous les moteurs de recherche, seul Google a une certaine capacité à afficher un site et à prendre en compte son JS avant d'afficher une page. De plus, bien que Google puisse afficher la page d'index de votre site, on sait qu'il peut y avoir des problèmes pour naviguer sur les sites équipés d'un routeur.



Cela signifie qu'il sera très difficile pour votre site de grimper en tête des résultats de tout moteur de recherche autre que Google.



Le même problème peut être observé sur les réseaux sociaux, par exemple sur Facebook - si un lien vers votre site est partagé, alors ni son nom ni l'image d'aperçu ne s'afficheront correctement.



Comment résoudre ce problème



Il existe plusieurs façons de le résoudre.



A - Essayez de garder toutes les pages clés de votre site statiques



Lors de la création d'un site plateforme, où l'utilisateur devra se connecter avec son nom d'utilisateur, et sans se connecter au système, le contenu n'est pas fourni au visiteur, vous pouvez essayer de laisser des pages publiques statiques (écrites en HTML) de votre site, en particulier, l'index, "à propos de nous", "contacts "Et n'utilisez pas JS lors de leur affichage .



Étant donné que votre contenu est limité par les exigences de connexion, il ne sera pas indexé par les moteurs de recherche et ne pourra pas être partagé sur les réseaux sociaux.



B - Générez des parties de votre application sous forme de pages HTML lors de la construction



Vous pouvez ajouter des bibliothèques telles que react-snapshot à votre projet ; ils sont utilisés pour générer des copies HTML des pages de votre application et les stocker dans un répertoire dédié. Ce répertoire est ensuite déployé avec le package JS. Ainsi, le HTML sera servi à partir du serveur avec la réponse, et votre site sera vu par les utilisateurs qui ont désactivé JavaScript, ainsi que par les moteurs de recherche, etc.



En règle générale, il n'est pas difficile de configurer react-snapshot: ajoutez simplement la bibliothèque à votre projet et modifiez le script de construction comme suit:



"build": "webpack && react-snapshot --build-dir static"





L'inconvénient de cette solution est le suivant: tout le contenu que nous voulons générer doit être disponible au moment de la construction - nous ne pouvons accéder à aucune API pour l'obtenir, nous ne pouvons pas non plus pré-générer du contenu qui dépend des données fournies par l'utilisateur (par exemple à partir d'une URL).



C - Créer une application JS qui utilise le rendu serveur



L'un des principaux arguments de vente de la génération actuelle d'applications JS est qu'elles peuvent être exécutées à la fois sur le client (navigateur) et sur le serveur. Cela permet de générer du HTML pour des pages plus dynamiques, celles dont le contenu n'est pas encore connu au moment de la construction. Ces applications sont souvent appelées "isomorphes" ou "universelles".



Les deux solutions de rendu côté serveur les plus populaires pour React sont:





Créez votre propre implémentation SSR



Important: si vous essayez de créer vous-même votre propre implémentation SSR pour les applications React, vous devrez fournir un nœud backend pour votre serveur. Vous ne pourrez pas déployer cette solution sur un hôte statique comme vous le feriez avec les pages github.



La première chose à faire est de créer une application, comme toute autre application React.



Créons un point d'entrée:



// index.js
import React from 'react';
import { render } from 'react-dom';
import App from './App.js';render(<App />, document.getElementById('root'));
      
      







Et le composant-application (App):



// App.js
import React from 'react';const App = () => {
  return (
    <div>
      Welcome to SSR powered React application!
    </div>
  );
}
      
      





Et aussi un "wrapper" pour charger notre application:



// index.html
<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
  </head>
  <body>
    <div id="root"></div>
    <script src="/bundle.js"></script>
  </body>
</html>
      
      





Comme vous pouvez le voir, l'application est assez simple. Dans cet article, nous ne passerons pas par toutes les étapes nécessaires pour générer l'assembly webpack + babel correct.

Si vous lancez l'application dans son état actuel, un message de bienvenue apparaîtra à l'écran. En regardant la source, vous verrez le contenu du fichier index.html



, mais le message de bienvenue n'y sera pas. Pour résoudre ce problème, ajoutons un rendu serveur. Tout d'abord, ajoutons 3 packages:



yarn add express pug babel-node --save-dev
      
      





Express est un serveur Web puissant pour node, pug est un moteur de template qui peut être utilisé avec express et babel-node est un wrapper pour node qui fournit une transpilation à la volée.



Tout d'abord, copions notre fichier index.html



et enregistrez-le sous index.pug



:



// index.pug
<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
  </head>
  <body>
    <div id="root">!{app}</div>
    <script src="bundle.js"></script>
  </body>
</html>
      
      





Comme vous pouvez le voir, le fichier n'a pas beaucoup changé, à l'exception de ce qui est maintenant inséré dans le HTML !{app}



. Il s'agit d'une variable pug



qui sera ultérieurement remplacée par le HTML réel.



Créons notre serveur:



// server.jsimport React from 'react';
import { renderToString } from 'react-dom/server';
import express from 'express';
import path from 'path';import App from './src/App';const app = express();
app.set('view engine', 'pug');
app.use('/', express.static(path.join(__dirname, 'dist')));app.get('*', (req, res) => {
  const html = renderToString(
    <App />
  );  res.render(path.join(__dirname, 'src/index.pug'), {
    app: html
  });
});app.listen(3000, () => console.log('listening on port 3000'));
      
      





Analysons ce fichier dans l'ordre.



import { renderToString } from 'react-dom/server';
      
      





La bibliothèque react-dom contient une exportation nommée distincte renderToString



qui fonctionne comme celle que nous connaissons render



, mais elle ne rend pas le DOM, mais HTML sous forme de chaîne.



const app = express();
app.set('view engine', 'pug');
app.use('/', express.static(path.join(__dirname, 'dist')));
      
      







Nous créons une nouvelle instance de serveur express et lui disons que nous allons utiliser un moteur de création de modèles pug



. Dans ce cas, nous pourrions nous débrouiller avec la lecture habituelle du fichier et effectuer l'opération "rechercher et remplacer", mais cette approche est vraiment inefficace et peut causer des problèmes en raison d'un accès multiple au système de fichiers ou de problèmes de mise en cache.



Dans la dernière ligne, nous disons à express de rechercher un fichier dans un répertoire dist



, et si la requête (par exemple /bundle.js



) correspond à un fichier présent dans ce répertoire, alors renvoyez-le.



app.get('*', (req, res) => {
});
      
      







Maintenant, nous disons à express d'ajouter un gestionnaire à chaque URL sans correspondance - y compris notre fichier inexistant index.html



(comme vous vous en souvenez, nous l'avons renommé index.pug



et il n'est pas dans le répertoire dist



).



const html = renderToString(
  <App />
);
      
      





Avec l'aide, renderToString



nous affichons notre application. Le code ressemble exactement au point d'entrée, mais une telle correspondance est facultative.



res.render(path.join(__dirname, 'src/index.pug'), {
  app: html
});
      
      





Maintenant que nous avons rendu le HTML, nous demandons à express de rendre le fichier en réponse index.pug



et de remplacer la variable app



par le HTML que nous avons reçu.



app.listen(3000, () => console.log('listening on port 3000'));
      
      





Enfin, nous nous assurons que le serveur démarre et le configurons pour qu'il écoute sur le port 3000.

Il ne nous reste plus qu'à ajouter le script requis à package.json



:



"scripts": {
  "server": "babel-node server.js"
}
      
      





Maintenant, en appelant yarn run server



, nous devrions recevoir la confirmation que le serveur fonctionne bien. Accédez à localhost : 3000 dans le navigateur , où, encore une fois, nous devrions voir notre application. Si nous regardons le code source à ce stade, nous voyons:



<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
  </head>
  <body>
    <div id="root">div data-reactroot="">Welcome to SSR powered React application!</div></div>
    <script src="bundle.js"></script>
  </body>
</html>
      
      





Si tout ressemble à ceci, cela signifie que le rendu du serveur fonctionne comme prévu et que vous pouvez commencer à étendre votre application!



Pourquoi avons-nous encore besoin de bundle.js?



Dans le cas d'une application extrêmement simple, considérée ici, il n'est pas nécessaire d'inclure bundle.js - sans ce fichier, notre application fonctionnera toujours. Mais dans le cas d'une vraie application, vous devez toujours inclure ce fichier.



Cela permettra aux navigateurs capables de gérer JavaScript de prendre en charge le travail, puis d'interagir avec votre page déjà côté client, et ceux qui ne savent pas comment analyser JS iront à la page avec le HTML souhaité que le serveur a renvoyé.



Choses dont il faut se rappeler



Malgré le fait que le rendu du serveur semble assez simple, lors du développement d'applications, vous devez faire attention à certains sujets qui à première vue ne sont pas tout à fait évidents:



  • , , . , , HTML, this.state



    ,
  • componentDidMount



    — , , . , , . , ( res.render



    ) , . -
  • react (. @reach/router react-router) , URL, . !



All Articles