Representación de reacción del lado del servidor | Programar Plus

React es mejor conocido como un marco de JavaScript del lado del cliente, pero ¿sabía que puede (¡y tal vez debería!) Renderizar React en el lado del servidor?

Suponga que ha creado una aplicación React de lista de eventos nueva y rápida para un cliente. La aplicación está conectada a una API construida con su herramienta del lado del servidor favorita. Un par de semanas después, el cliente le dice que sus páginas no aparecen en Google y no se ven bien cuando se publican en Facebook. Parece solucionable, ¿verdad?

Se da cuenta de que para resolver esto necesitará renderizar sus páginas de React desde el servidor en la carga inicial para que los rastreadores de los motores de búsqueda y los sitios de redes sociales puedan leer su marcado. Existe evidencia que muestra que Google a veces ejecuta javascript y puede indexar el contenido generado, pero no siempre. Por lo tanto, siempre se recomienda la renderización del lado del servidor si desea garantizar un buen SEO y compatibilidad con otros servicios como Facebook, Twitter.

En este tutorial, lo guiaremos a través de un ejemplo de representación del lado del servidor paso a paso. incluido trabajar alrededor de un obstáculo común para las aplicaciones React que se comunican con las API.

Los beneficios de la representación del lado del servidor

El SEO puede ser la conversación que hace que su equipo comience a hablar sobre la representación del lado del servidor, pero no es el único beneficio potencial.

Aquí está el grande: La representación del lado del servidor muestra las páginas más rápido. Con la renderización del lado del servidor, la respuesta de su servidor al navegador es el HTML de su página que está listo para ser renderizado para que el navegador pueda comenzar a renderizar sin tener que esperar a que se descargue y ejecute todo el JavaScript. No hay una “página en blanco” mientras el navegador descarga y ejecuta el JavaScript y otros activos necesarios para representar la página, que es lo que podría suceder en un sitio React completamente representado por el cliente.

Empezando

Veamos cómo agregar renderizado del lado del servidor a una aplicación React renderizada por un cliente básico con Babel y webpack. Nuestra aplicación tendrá la complejidad adicional de obtener los datos de una API de terceros.

Nota del editor: Esta publicación fue de una empresa de CMS, y recibí algunos correos electrónicos bastante fraudulentos que considero muy poco atractivos, por lo que eliminaré todas las referencias a ellos en este artículo y las reemplazaré con terminología genérica de “CMS”.

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;

Esto es lo que se incluye en el código de inicio:

  • `package.json` – para dependencias
  • Configuración de Webpack y Babel
  • `index.html`: el HTML de la aplicación
  • `index.js` – carga React y renderiza el Hello componente

Para que la aplicación se ejecute, primero clone el repositorio:

git clone ...
cd ..

Instale las dependencias:

npm install

Luego inicie el servidor de desarrollo:

npm run start

Navegar a http://localhost:3000 para ver la aplicación:

Si ve el código fuente de la página renderizada, verá que el marcado enviado al navegador es solo un enlace a un archivo JavaScript. Esto significa que no se garantiza que los motores de búsqueda y las plataformas de redes sociales puedan rastrear el contenido de la página:

Agregar renderizado del lado del servidor

A continuación, implementaremos la representación del lado del servidor para que el HTML completamente generado se envíe al navegador.

Para comenzar, instalaremos Express, un marco de aplicación del lado del servidor Node.js:

npm install express --save

Queremos crear un servidor que renderice nuestro componente React:

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

Analicemos lo que está sucediendo …

El handleRender La función maneja todas las solicitudes. La clase ReactDOMServer importada en la parte superior del archivo proporciona la renderToString() método que convierte un elemento React en su HTML inicial.

ReactDOMServer.renderToString(<Hello />);

Esto devuelve el HTML para el Hello componente, que inyectamos en el HTML de index.html para generar el HTML completo para la página en el servidor.

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

Para iniciar el servidor, actualice el script de inicio en `package.json` y luego ejecute npm run start:

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

Navegar a http://localhost:3000 para ver la aplicación. ¡Voila! Su página ahora se está procesando desde el servidor. Pero hay un problema. Si ve la fuente de la página en el navegador. Notarás que las publicaciones del blog aún no están incluidas en la respuesta. ¿Que esta pasando? Si abrimos la pestaña de red en Chrome, veremos que la solicitud de API está sucediendo en el cliente.

Aunque estamos procesando el componente React en el servidor, la solicitud de la API se realiza de forma asincrónica en componentWillMount y el componente se procesa antes de que se complete la solicitud. Entonces, aunque estamos renderizando en el servidor, solo lo estamos haciendo parcialmente. Resulta que hay un problema en el repositorio de React con más de 100 comentarios que discuten el problema y varias soluciones.

Obteniendo datos antes de renderizar

Para solucionar este problema, debemos asegurarnos de que la solicitud de API se complete antes de Hello se renderiza el componente. Esto significa hacer la solicitud de la API fuera del ciclo de representación del componente de React y obtener datos antes de que rendericemos el componente.

Para mover la obtención de datos antes de renderizar, instalaremos react-transmit:

npm install react-transmit --save

React Transmit nos brinda elegantes componentes de envoltura (a menudo denominados “componentes de orden superior”) para obtener datos que funcionan en el cliente y el servidor.

Así es como se ve nuestro componente con React Transmit implementado:

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);
    }
  }
});

Hemos envuelto nuestro componente en un componente de orden superior que obtiene datos utilizando Transmit.createContainer. Hemos eliminado los métodos del ciclo de vida del componente React ya que no es necesario recuperar datos dos veces. Y hemos cambiado el render método a utilizar props referencias en lugar de state, ya que React Transmit pasa datos al componente como accesorios.

Para asegurarnos de que el servidor obtenga datos antes de renderizar, importamos Transmitir y usar Transmit.renderToString en vez de ReactDOM.renderToString método.

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

Reinicie el servidor, busque http://localhost:3000. Vea la fuente de la página y verá que la página ahora se está renderizando completamente en el servidor.

Ir más lejos

¡Lo hicimos! Usar React en el servidor puede ser complicado, especialmente cuando se obtienen datos de API. Afortunadamente, la comunidad de React está prosperando y está creando muchas herramientas útiles. Si está interesado en marcos para crear grandes aplicaciones React que se procesan en el cliente y el servidor, consulte Electrode de Walmart Labs o Next.js. O si desea renderizar React en Ruby, consulte Hypernova de AirBnB.