Was ist serverseitiges Rendering und brauche ich es?

Hallo Habr!



Beginnen wir unser Gespräch im neuen Jahr mit einem Startartikel zum serverseitigen Rendern. Wenn Sie interessiert sind, ist eine neuere Veröffentlichung über Nuxt.js und weitere Veröffentlichungsarbeiten in dieser Richtung möglich.



Mit dem Aufkommen moderner JavaScript-Frameworks und -Bibliotheken, die hauptsächlich zum Erstellen interaktiver Webseiten und Anwendungen für einzelne Seiten bestimmt sind, hat sich der gesamte Prozess des Anzeigens von Seiten für den Benutzer stark verändert.



Vor dem Aufkommen vollständig von JS generierter Anwendungen im Browser wurde dem Client HTML als Antwort auf einen HTTP-Aufruf bereitgestellt. Dies kann durch Rückgabe einer statischen HTML-Datei mit dem Inhalt oder durch dynamischere Verarbeitung der Antwort in einer serverseitigen Sprache (PHP, Python oder Java) erfolgen.



Mit dieser Lösung können Sie reaktionsfähige Sites erstellen, die viel schneller als Standard-Request-Response-Sites ausgeführt werden, da die Zeit, die die Anfrage "unterwegs" benötigt, entfällt.



Eine typische Antwort des Servers auf eine Anfrage an eine in React geschriebene Site sieht ungefähr so ​​aus:



<!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>
      
      





Durch Auswahl dieser Antwort wählt unser Browser auch das "Paket" aus, app.js



das unsere Anwendung enthält, und nach ein oder zwei Sekunden wird die gesamte Seite gerendert.



Zu diesem Zeitpunkt können Sie bereits den integrierten HTML-Inspektor des Browsers verwenden, um den gesamten gerenderten HTML-Code anzuzeigen. Wenn wir uns jedoch den Quellcode ansehen, sehen wir nichts anderes als den obigen HTML-Code.



Warum ist das ein Problem?



Während dieses Verhalten für die meisten unserer Benutzer oder bei der Entwicklung einer Anwendung kein Problem darstellt, kann es unerwünscht werden, wenn:



  • -, ,
  • , ,


Wenn Ihre Zielgruppe aus demografischer Sicht zu einer dieser Gruppen gehört, ist die Arbeit mit der Website unpraktisch. Insbesondere müssen Benutzer lange warten und auf das Schild „Laden ...“ (oder noch schlimmer auf einen leeren Bildschirm) starren.



"Okay, aber demografisch gesehen ist meine Zielgruppe definitiv keine dieser Gruppen. Sollte ich mir also Sorgen machen?"



Bei der Arbeit mit einer vom Kunden gerenderten Anwendung sind zwei weitere Dinge zu beachten: Suchmaschinen und Präsenz in sozialen Medien .



Von allen Suchmaschinen kann heute nur Google eine Website anzeigen und deren JS berücksichtigen, bevor eine Seite angezeigt wird. Während Google die Indexseite Ihrer Website anzeigen kann, ist bekannt, dass beim Navigieren auf Websites mit einem Router möglicherweise Probleme auftreten.



Dies bedeutet, dass es für Ihre Website sehr schwierig sein wird, an die Spitze der Ergebnisse einer anderen Suchmaschine als Google zu gelangen.



Das gleiche Problem tritt in sozialen Netzwerken auf, z. B. auf Facebook. Wenn ein Link zu Ihrer Website freigegeben wird, werden weder der Name noch das Vorschaubild ordnungsgemäß angezeigt.



So lösen Sie dieses Problem



Es gibt verschiedene Möglichkeiten, dies zu lösen.



A - Versuchen Sie, alle Schlüsselseiten Ihrer Site statisch zu halten



Wenn eine Plattform-Site erstellt wird, auf der sich der Benutzer mit seinem Benutzernamen anmelden muss und ohne sich beim System anzumelden, wird der Inhalt dem Besucher nicht zur Verfügung gestellt. Sie können versuchen , statische (in HTML geschriebene) öffentliche Seiten Ihrer Site zu belassen, insbesondere den Index "Über uns", "Kontakte" "Und verwenden Sie JS nicht, wenn Sie sie anzeigen .



Da Ihr Inhalt durch die Anmeldeanforderungen eingeschränkt ist, wird er nicht von Suchmaschinen indiziert und kann nicht in sozialen Medien geteilt werden.



B - Generieren Sie Teile Ihrer Anwendung während der Erstellung als HTML-Seiten



Sie können Ihrem Projekt Bibliotheken wie React-Snapshot hinzufügen . Sie werden verwendet, um HTML-Kopien der Seiten Ihrer Anwendung zu erstellen und in einem dedizierten Verzeichnis zu speichern. Dieses Verzeichnis wird dann zusammen mit dem JS-Paket bereitgestellt. Auf diese Weise wird HTML zusammen mit der Antwort vom Server bereitgestellt, und Ihre Website wird von Benutzern mit deaktiviertem JavaScript sowie von Suchmaschinen usw. angezeigt.



In der Regel ist das Konfigurieren des React-Snapshots nicht schwierig: Fügen Sie einfach die Bibliothek zu Ihrem Projekt hinzu und ändern Sie das Build-Skript wie folgt:



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





Der Nachteil dieser Lösung ist folgender: Alle Inhalte, die wir generieren möchten, müssen zum Zeitpunkt der Erstellung verfügbar sein. Wir können nicht auf eine API zugreifen, um sie abzurufen. Wir können auch keine Inhalte vorgenerieren, die von den vom Benutzer bereitgestellten Daten abhängen (zum Beispiel von einer URL).



C - Erstellen Sie eine JS-Anwendung, die Server-Rendering verwendet



Eines der größten Verkaufsargumente der heutigen Generation von JS-Anwendungen ist, dass sie sowohl auf dem Client (Browser) als auch auf dem Server ausgeführt werden können. Auf diese Weise kann HTML für dynamischere Seiten generiert werden, deren Inhalt zum Zeitpunkt der Erstellung noch nicht bekannt ist. Diese Anwendungen werden oft als "isomorph" oder "universell" bezeichnet.



Die beiden beliebtesten serverseitigen Rendering-Lösungen für React sind:





Erstellen Sie Ihre eigene SSR-Implementierung



Wichtig: Wenn Sie versuchen möchten, Ihre eigene SSR-Implementierung für React-Anwendungen selbst zu erstellen, müssen Sie ein Node-Backend für Ihren Server bereitstellen. Sie können diese Lösung nicht wie bei Github-Seiten auf einem statischen Host bereitstellen.



Das erste, was wir tun müssen, ist eine Anwendung zu erstellen, genau wie jede andere React-Anwendung.



Erstellen wir einen Einstiegspunkt:



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







Und die Komponentenanwendung (App):



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





Und auch ein "Wrapper" zum Laden unserer Anwendung:



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





Wie Sie sehen können, ist die Anwendung ziemlich einfach. In diesem Artikel werden nicht alle Schritte ausgeführt, die zum Generieren der richtigen Webpack + Babel-Assembly erforderlich sind.

Wenn Sie die Anwendung im aktuellen Status starten, wird eine Begrüßungsnachricht auf dem Bildschirm angezeigt. Wenn Sie sich den Quellcode ansehen, sehen Sie den Inhalt der Datei index.html



, aber die Begrüßungsnachricht ist nicht vorhanden. Um dieses Problem zu lösen, fügen wir Server-Rendering hinzu. Fügen wir zunächst 3 Pakete hinzu:



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





Express ist ein leistungsstarker Webserver für Node, Pug ist eine Template-Engine, die mit Express verwendet werden kann, und Babel-Node ist ein Wrapper für Node, der On-the-Fly-Transpilation bietet.



Kopieren wir zunächst unsere Datei index.html



und speichern sie als 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>
      
      





Wie Sie sehen können, hat sich an der Datei nicht viel geändert, außer dem, was jetzt in den HTML-Code eingefügt wird !{app}



. Dies ist eine Variable pug



, die später durch den tatsächlichen HTML-Code ersetzt wird.



Lassen Sie uns unseren Server erstellen:



// 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'));
      
      





Lassen Sie uns diese Datei der Reihe nach analysieren.



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





Die React-Dom-Bibliothek enthält einen separaten benannten Export renderToString



, der wie der uns bekannte render



funktioniert, jedoch nicht das DOM, sondern HTML als Zeichenfolge rendert.



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







Wir erstellen eine neue Express-Server-Instanz und teilen ihr mit, dass wir eine Template-Engine verwenden werden pug



. In diesem Fall könnten wir mit dem üblichen Lesen der Datei und dem Ausführen des Vorgangs "Suchen und Ersetzen" auskommen, aber dieser Ansatz ist wirklich ineffektiv und kann Probleme aufgrund des Mehrfachzugriffs auf das Dateisystem oder Probleme beim Zwischenspeichern verursachen.



In der letzten Zeile weisen wir express an, nach einer Datei in einem Verzeichnis zu suchen. dist



Wenn die Anforderung (zum Beispiel /bundle.js



) mit einer in diesem Verzeichnis vorhandenen Datei übereinstimmt, geben Sie sie zurück.



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







Jetzt weisen wir express an, jeder nicht übereinstimmenden URL einen Handler hinzuzufügen - einschließlich unserer nicht vorhandenen Datei index.html



(wie Sie sich erinnern, haben wir sie in umbenannt index.pug



und sie befindet sich nicht im Verzeichnis dist



).



const html = renderToString(
  <App />
);
      
      





Mit Hilfe zeigen renderToString



wir unsere Anwendung an. Der Code sieht genauso aus wie der Einstiegspunkt, eine solche Übereinstimmung ist jedoch optional.



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





Nachdem wir HTML gerendert haben, weisen wir express an, die Datei als Antwort zu rendern index.pug



und die Variable app



durch das empfangene HTML zu ersetzen .



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





Schließlich ermöglichen wir dem Server, ihn zu starten und so zu konfigurieren, dass er Port 3000 überwacht.

Jetzt müssen wir nur noch das erforderliche Skript hinzufügen zu package.json



:



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





Jetzt yarn run server



sollten wir durch einen Anruf eine Bestätigung erhalten, dass der Server tatsächlich läuft. Gehen Sie zum Browser bei localhost : 3000, wo wir wieder unsere Anwendung sehen sollten. Wenn wir uns zu diesem Zeitpunkt den Quellcode ansehen, sehen wir:



<!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>
      
      





Wenn alles so aussieht, bedeutet dies, dass das Server-Rendering wie erwartet funktioniert und Sie mit der Erweiterung Ihrer Anwendung beginnen können!



Warum brauchen wir noch bundle.js?



Im Fall einer solch sehr einfachen Anwendung, die hier betrachtet wird, ist es nicht erforderlich, bundle.js einzuschließen - ohne diese Datei funktioniert unsere Anwendung weiterhin. Bei einer echten Anwendung müssen Sie diese Datei jedoch noch einschließen.



Auf diese Weise können Browser, die mit JavaScript umgehen können, die Arbeit übernehmen und dann mit Ihrer Seite interagieren, die sich bereits auf der Clientseite befindet. Wer nicht weiß, wie JS analysiert wird, wechselt zu der Seite mit dem gewünschten HTML-Code, den der Server zurückgegeben hat.



Dinge, an die man sich erinnern sollte



Trotz der Tatsache, dass das Rendern von Servern bei der Entwicklung von Anwendungen recht einfach aussieht, müssen Sie einige Themen berücksichtigen, die auf den ersten Blick nicht ganz offensichtlich sind:



  • , , . , , HTML, this.state



    ,
  • componentDidMount



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



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



All Articles