Serverseitiges React-Rendering

Avatar of Roger Jin
Roger Jin am

DigitalOcean bietet Cloud-Produkte für jede Phase Ihrer Reise. Starten Sie mit 200 $ kostenlosem Guthaben!

React ist am besten als clientseitiges JavaScript-Framework bekannt, aber wussten Sie, dass Sie React serverseitig rendern können (und vielleicht sogar sollten!)?

Stellen Sie sich vor, Sie haben für einen Kunden eine blitzschnelle neue React-App zur Veranstaltungsliste entwickelt. Die App ist mit einem von Ihnen bevorzugten serverseitigen Tool an eine API angebunden. Ein paar Wochen später teilt Ihnen der Kunde mit, dass seine Seiten nicht bei Google angezeigt werden und beim Posten auf Facebook nicht gut aussehen. Scheint lösbar, oder?

Sie stellen fest, dass Sie, um dies zu lösen, Ihre React-Seiten beim ersten Laden vom Server rendern müssen, damit Crawler von Suchmaschinen und Social-Media-Seiten Ihr Markup lesen können. Es gibt Hinweise darauf, dass Google *manchmal* JavaScript ausführt und den generierten Inhalt indizieren kann, aber nicht immer. Daher wird serverseitiges Rendering immer empfohlen, wenn Sie eine gute SEO und Kompatibilität mit anderen Diensten wie Facebook und Twitter gewährleisten möchten.

In diesem Tutorial führen wir Sie Schritt für Schritt durch ein serverseitiges Rendering-Beispiel, einschließlich der Überwindung eines häufigen Hindernisses für React-Apps, die mit APIs kommunizieren.

Die Vorteile von Server-Side Rendering

SEO mag der Grund sein, der Ihr Team dazu bringt, über serverseitiges Rendering zu sprechen, aber es ist nicht der einzige potenzielle Vorteil.

Hier ist der wichtigste: **Serverseitiges Rendering zeigt Seiten schneller an**. Beim serverseitigen Rendering ist die Antwort Ihres Servers an den Browser das HTML Ihrer Seite, das zum Rendern bereit ist, sodass der Browser mit dem Rendern beginnen kann, ohne auf den Download und die Ausführung des gesamten JavaScripts warten zu müssen. Es gibt keine "weiße Seite", während der Browser das JavaScript und andere für das Rendern der Seite benötigte Assets herunterlädt und ausführt, was bei einer rein clientseitig gerenderten React-Site passieren könnte.

Erste Schritte

Gehen wir durch, wie Sie serverseitiges Rendering zu einer einfachen clientseitig gerenderten React-App mit Babel und webpack hinzufügen. Unsere App wird die zusätzliche Komplexität des Abrufens von Daten von einer Drittanbieter-API mit sich bringen.

Hinweis des Herausgebers: Dieser Beitrag stammt von einem CMS-Unternehmen, und ich habe ziemlich spammy E-Mails von ihnen erhalten, die ich als sehr uncool empfinde. Daher entferne ich alle Verweise auf sie in diesem Artikel und ersetze sie durch generische "CMS"-Terminologie.

import React from 'react';
import cms from 'cms';

const content = cms('b60a008584313ed21803780bc9208557b3b49fbb');

var Hello = React.createClass({
  getInitialState: function() {
    return {loaded: false};
  },
  componentWillMount: function() {
    content.post.list().then((resp) => {
      this.setState({
        loaded: true,
        resp: resp.data
      })
    });
  },
  render: function() {
    if (this.state.loaded) {
      return (
        <div>
          {this.state.resp.data.map((post) => {
            return (
              <div key={post.slug}>{post.title}</div>
            )
          })}
        </div>
      );
    } else {
      return <div>Loading...</div>;
    }
  }
});

export default Hello;

Hier ist, was der Starter-Code noch enthält

  • `package.json` – für Abhängigkeiten
  • Webpack- und Babel-Konfiguration
  • `index.html` – das HTML für die App
  • `index.js` – lädt React und rendert die Hello-Komponente

Um die App zum Laufen zu bringen, klonen Sie zuerst das Repository

git clone ...
cd ..

Installieren Sie die Abhängigkeiten

npm install

Starten Sie dann den Entwicklungsserver

npm run start

Rufen Sie https://:3000 auf, um die App anzuzeigen

Wenn Sie den Quellcode der gerenderten Seite anzeigen, werden Sie feststellen, dass das an den Browser gesendete Markup nur ein Link zu einer JavaScript-Datei ist. Dies bedeutet, dass die Inhalte der Seite nicht garantiert von Suchmaschinen und Social-Media-Plattformen gecrawlt werden können

Hinzufügen von Server Side Rendering

Als Nächstes implementieren wir serverseitiges Rendering, damit vollständig generiertes HTML an den Browser gesendet wird.

Um zu beginnen, installieren wir Express, ein serverseitiges Anwendungsframework für Node.js

npm install express --save

Wir möchten einen Server erstellen, der unsere React-Komponente rendert

import express from 'express';
import fs from 'fs';
import path from 'path';
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import Hello from './Hello.js';

function handleRender(req, res) {
  // Renders our Hello component into an HTML string
  const html = ReactDOMServer.renderToString(<Hello />);

  // Load contents of index.html
  fs.readFile('./index.html', 'utf8', function (err, data) {
    if (err) throw err;

    // Inserts the rendered React HTML into our main div
    const document = data.replace(/<div id="app"><\/div>/, `<div id="app">${html}</div>`);

    // Sends the response back to the client
    res.send(document);
  });
}

const app = express();

// Serve built files with static files middleware
app.use('/build', express.static(path.join(__dirname, 'build')));

// Serve requests with our handleRender function
app.get('*', handleRender);

// Start server
app.listen(3000);

Lassen Sie uns aufschlüsseln, was passiert...

Die Funktion handleRender verarbeitet alle Anfragen. Die Klasse ReactDOMServer, die am Anfang der Datei importiert wird, stellt die Methode renderToString() bereit, die ein React-Element in sein anfängliches HTML rendert.

ReactDOMServer.renderToString(<Hello />);

Dies gibt das HTML für die Hello-Komponente zurück, das wir in das HTML von index.html einfügen, um das vollständige HTML für die Seite auf dem Server zu generieren.

const document = data.replace(/<div id="app"><\/div>/, `<div id="app">${html}</div>`);

Um den Server zu starten, aktualisieren Sie das Startskript in `package.json` und führen Sie dann `npm run start` aus

"scripts": {
  "start": "webpack && babel-node server.js"
},

Rufen Sie https://:3000 auf, um die App anzuzeigen. Voilà! Ihre Seite wird jetzt vom Server gerendert. Aber es gibt ein Problem. Wenn Sie den Quellcode der Seite im Browser anzeigen. Sie werden feststellen, dass die Blogbeiträge immer noch nicht in der Antwort enthalten sind. Was ist los? Wenn wir den Netzwerk-Tab in Chrome öffnen, sehen wir, dass die API-Anfrage auf dem Client erfolgt.

Obwohl wir die React-Komponente auf dem Server rendern, wird die API-Anfrage *asynchron* in componentWillMount gemacht und die Komponente wird gerendert, *bevor* die Anfrage abgeschlossen ist. Obwohl wir also auf dem Server rendern, tun wir dies nur teilweise. Es stellt sich heraus, dass es ein Problem im React-Repository gibt, mit über 100 Kommentaren, die das Problem und verschiedene Workarounds diskutieren.

Daten vor dem Rendering abrufen

Um dies zu beheben, müssen wir sicherstellen, dass die API-Anfrage abgeschlossen ist, bevor die Hello-Komponente gerendert wird. Das bedeutet, die API-Anfrage außerhalb des Render-Zyklus der React-Komponente zu stellen und Daten abzurufen, bevor wir die Komponente rendern.

Um den Datenabruf vor das Rendering zu verlagern, installieren wir react-transmit

npm install react-transmit --save

React Transmit bietet uns elegante Wrapper-Komponenten (oft als "Higher-Order Components" bezeichnet) zum Abrufen von Daten, die sowohl auf dem Client als auch auf dem Server funktionieren.

So sieht unsere Komponente mit implementiertem React Transmit aus

import React from 'react';
import cms from 'cms'
import Transmit from 'react-transmit';

const content = cms('b60a008584313ed21803780bc9208557b3b49fbb');

var Hello = React.createClass({
  render: function() {
    if (this.props.posts) {
      return (
        <div>
          {this.props.posts.data.map((post) => {
            return (
              <div key={post.slug}>{post.title}</div>
            )
          })}
        </div>
      );
    } else {
      return <div>Loading...</div>;
    }
  }
});

export default Transmit.createContainer(Hello, {
  // These must be set or else it would fail to render
  initialVariables: {},
  // Each fragment will be resolved into a prop
  fragments: {
    posts() {
      return content.post.list().then((resp) => resp.data);
    }
  }
});

Wir haben unsere Komponente in eine Higher-Order-Komponente eingewickelt, die Daten mit Transmit.createContainer abruft. Wir haben die Lifecycle-Methoden aus der React-Komponente entfernt, da kein Grund besteht, Daten zweimal abzurufen. Und wir haben die render-Methode geändert, um props-Referenzen anstelle von state zu verwenden, da React Transmit die Daten als Props an die Komponente übergibt.

Um sicherzustellen, dass der Server Daten vor dem Rendering abruft, importieren wir Transmit und verwenden Transmit.renderToString anstelle der ReactDOM.renderToString-Methode.

import express from 'express';
import fs from 'fs';
import path from 'path';
import React from 'react';
import ReactDOMServer from 'react-dom/server';
import Hello from './Hello.js';
import Transmit from 'react-transmit';

function handleRender(req, res) {
  Transmit.renderToString(Hello).then(({reactString, reactData}) => {
    fs.readFile('./index.html', 'utf8', function (err, data) {
      if (err) throw err;

      const document = data.replace(/<div id="app"><\/div>/, `<div id="app">${reactString}</div>`);
      const output = Transmit.injectIntoMarkup(document, reactData, ['/build/client.js']);

      res.send(document);
    });
  });
}

const app = express();

// Serve built files with static files middleware
app.use('/build', express.static(path.join(__dirname, 'build')));

// Serve requests with our handleRender function
app.get('*', handleRender);

// Start server
app.listen(3000);

Starten Sie den Server neu, rufen Sie https://:3000 auf. Zeigen Sie den Quellcode der Seite an und Sie werden sehen, dass die Seite jetzt vollständig auf dem Server gerendert wird!

Weiter geht's

Wir haben es geschafft! Die Verwendung von React auf dem Server kann knifflig sein, insbesondere beim Abrufen von Daten aus APIs. Glücklicherweise ist die React-Community sehr aktiv und entwickelt viele hilfreiche Tools. Wenn Sie an Frameworks für die Erstellung großer React-Apps interessiert sind, die auf Client und Server gerendert werden, schauen Sie sich Electrode von Walmart Labs oder Next.js an. Oder wenn Sie React in Ruby rendern möchten, schauen Sie sich AirBnBs Hypernova an.