Tutorial: Serverseitiges Rendering mit React – Teil 2: React Router und React Helmet

2 Kommentare

Nachdem wir im ersten Teil (Tutorial: Serverseitiges Rendering mit React – Teil 1: Einführung) die grundlegende Konfiguration für unsere serverseitig gerenderte App umgesetzt haben, schauen wir uns in diesem Teil an, wie wir React Router auf dem Server nutzen können, um genau die Seiten zu rendern, die der User angefragt hat. Außerdem lernen wir, wie React Helmet für das Setzen von Meta-Tags auf dem Server konfiguriert werden muss.

Der Code der fertigen SSR-App kann hier abgerufen werden:
https://gitlab.codecentric.de/rene.bohrenfeldt/react-serverside-rendering-example

2. React Router

Wir wollen sicherstellen, dass der User beim Abrufen der Seiten „/“, „/ueber-uns“, „/impressum“ etc. immer auch genau die richtigen statischen Inhalte vom Server geliefert bekommt. Im Client kümmert sich React Router darum, die richtigen Komponenten anzuzeigen, je nachdem, welche Route angesurft wird. Dieses Verhalten müssen wir nun auch auf dem Server ermöglichen.

Wenn wir React Router v4 in unseren bisherigen Projekt eingesetzt haben, dann kennen wir bereits das Modul BrowserRouter. Wahrscheinlich enthält unsere clientseitige index.js im „src/“-Verzeichnis einen ähnlichen Code wie diesen hier:

src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
import Routes from './Routes';
 
ReactDOM.render(
 <BrowserRouter>
   <Routes />
 </BrowserRouter>,
 document.querySelector('#app')
);

Dieser kann fast genau so bestehen bleiben. Wir ändern an dieser Stelle nur die Methode ReactDOM.render() zu ReactDOM.hydrate(), um zu verhindern, dass uns React folgende unschöne Warnmeldung in der Browserkonsole anzeigt:

"Warning: render(): Calling ReactDOM.render() to hydrate server-rendered markup will stop working in React v17. Replace the ReactDOM.render() call with ReactDOM.hydrate() if you want React to attach to the server HTML."

ReactDOM.hydrate() ist genau für unseren Anwendungsfall zugeschnitten. Es geht von einem serverseitig gerenderten DOM aus und prüft lediglich, ob sich am bestehenden DOM etwas geändert hat.

In unserer server.js wollen wir nun auch das Routing integrieren, um immer die richtige statische HTML-Seite auf dem Server zu rendern.

server.js
import express from 'express';
import React from "react";
import {StaticRouter} from "react-router-dom";import ReactDOM  from 'react-dom/server';
import Routes from "./src/Routes"; 
const app = express();
 
const renderer = req => { const content = ReactDOM.renderToString(
   <StaticRouter location={req.path} context={{}}>     <Routes />   </StaticRouter> );
 
 return `
    <html>
      <head></head>
      <body>
        <div id="app">${content}</div>
        <script src="bundle.js"></script>
      </body>
    </html>
  `;
 };
 
 app.use(express.static('public'));
 app.get('*', (req, res) => {
  res.send(renderer(req)); });
 
 app.listen(3000, () => {
  console.log('Server gestartet auf http://localhost:3000');
 });

Man sieht, dass wir innerhalb unserer renderToString()-Methode anstelle von BrowserRouter den StaticRouter nutzen. Wir gehen hier auch noch einen Schritt weiter und übergeben dem StaticRouter den Anfragepfad, der uns über req.path von express zur Verfügung gestellt wird. Damit weiß React Router, welche Seite gerendert werden soll und wir bekommen nicht immer die Startseite zurück geliefert. Der StaticRouter benötigt immer ein context-Property, den wir hier erstmal mit einem leeren Objekt definieren.

Mit diesen einfachen Änderungen haben wir erreicht, dass wir immer genau die Seite auf dem Server rendern, die der User anfragt. Als nächstes können wir uns um die Meta-Tags kümmern.

3. React Helmet Meta-Tags

React Helmet ist ein hilfreiches Tool, um für jede beliebige Page-Komponente Meta-Tags zu setzen. Normalerweise sieht eine Komponente, die React Helmet verwendet, so aus:

ToDoList.js
...
import { Helmet } from 'react-helmet';
 
class ToDoList extends Component {
...
 
 render() {
  return (
    <div>
      <Helmet>
        <title>Mein Titel</title>
        <meta name="description" content="Meine Beschreibung"/>
        <meta property="og:title" content="Ein etwas anderer Titel" />
      </Helmet>
      ...
    </div>
  );
 }
}

React Helmet ist also selbst eine einfache Komponente, die wir innerhalb unserer (Page-)Komponenten integrieren. Man kann innerhalb von <Helmet></Helmet> beliebige Meta-Tags definieren.

Beim SSR haben wir jedoch das Problem, dass diese Meta-Tags nicht ohne unser Zutun im <header>-Bereich gerendert werden. Um das zu erreichen müssen wir unsere server.js anpassen:

server.js
import express from 'express';
import React from "react";
import {StaticRouter} from "react-router-dom";
import ReactDOM  from 'react-dom/server';
import Routes from "./src/Routes";
import { Helmet } from 'react-helmet'; 
const app = express();
 
const renderer = (req, store) => {
 const content = ReactDOM.renderToString(
  <StaticRouter location={req.path} context={{}}>
    <Routes />
  </StaticRouter>
 );
 
 const helmet = Helmet.renderStatic(); 
 return `
   <html>
     <head>
        ${helmet.title.toString()}        ${helmet.meta.toString()}     </head>
     <body>
     <div id="app">${content}</div>
     <script src="bundle.js"></script>
   </body>
 </html>
 `;
};
 
app.use(express.static('public'));
app.get('*', (req, res) => {
  res.send(renderer(req));
});
 
app.listen(3000, () => {
 console.log('Server gestartet auf http://localhost:3000');
});

Nach dem Aufruf von ReactDOM.renderToString() enthält Helmet alle Informationen über die zu rendernden Meta-Tags. Deswegen können wir im Anschluss die Meta-Informationen einfach auslesen:

const helmet = Helmet.renderStatic();

Wie wir hier sehen, sind die Titel- und Meta-Informationen in zwei verschiedenen Properties am Helmet-Objekt verfügbar:

${helmet.title.toString()}
${helmet.meta.toString()}

Es gibt auch noch weitere Properties am Helmet-Objekt, die in der React Helmet Doku nachzulesen sind.

Durch diese kleine Anpassung sind nun auch unsere Meta-Tags im statisch gerenderten HTML enthalten.

Im letzten und umfangreichsten Teil der Serie (Tutorial: Serverseitiges Rendering mit React – Teil 3: Redux Store) beleuchten wir, wie wir den Redux Store serverseitig konfigurieren. Wir sehen uns außerdem an, wie wir dynamische Inhalte laden bevor wir das statische HTML erzeugen und wie wir den befüllten Redux State an den Client übergeben.

René Bohrenfeldt

René Bohrenfeldt ist seit mehr als 10 Jahren in der Software-Entwicklung tätig und fokussiert sich dabei hauptsächlich auf Frontend-Technologien. Bei der codecentric AG setzt er als IT-Consultant sowohl sein Entwicklungs-Know-how als auch seine Kommunikationsstärke als PO und Moderator ein.
Darüber hinaus engagiert sich der studierte Betriebswirt unternehmerisch und ist Mitgründer einer Busvermietung in Berlin.

Share on FacebookGoogle+Share on LinkedInTweet about this on TwitterShare on RedditDigg thisShare on StumbleUpon

Kommentare

  • Yidao

    3. Januar 2018 von Yidao

    Hey. Ich finde Dein Tutorial echt hilfreich! Dank sehr.
    Es sieht aus, (import Routes from „./client/Routes“;) in 1. server.js-Codestück (import Routes from „./src/Routes“;) sein sollte oder?

    • René Bohrenfeldt

      17. Januar 2018 von René Bohrenfeldt

      Hallo Yidao, vielen Dank für das Feedback. Du hast Recht, es muss tatsächlich: import Routes from „./src/Routes“ heißen. Danke 😉

Kommentieren

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.