Uno de los problemas que tendrá que resolver al escribir una aplicación de representación del lado del servidor es trabajar con las metaetiquetas que cada página debería tener, lo que ayuda a indexarlas por los motores de búsqueda.
Comenzando a google, la primera solución a la que se lo dirigirá
es probablemente
React Helmet .
Una de las ventajas de la biblioteca es que puede considerarse isomórfica de alguna manera y puede usarse perfectamente tanto en el lado del cliente como en el lado del servidor.
class Page extends Component { render() { return ( <div> <Helmet> <title>Turbo Todo</title> <meta name="theme-color" content="#008f68" /> </Helmet> {/* ... */} </div> ); } }
En el servidor, el enrutador se verá así:
app.get('/*', (req, res) => { const html = renderToString(<App />); const helmet = Helmet.renderStatic(); res.send(` <!doctype html> <html ${helmet.htmlAttributes.toString()}> <head> ${helmet.title.toString()} ${helmet.meta.toString()} </head> <body ${helmet.bodyAttributes.toString()}> <div id="app">${html}</div> </body> </html> `); });
Ambos fragmentos son completamente correctos y funcionales, pero hay un PERO, el código anterior para el servidor es completamente sincrónico y, por lo tanto, completamente seguro, pero si se vuelve asíncrono, ocultará errores en sí mismos difícilmente depurados:
app.get('/*', async (req, res) => {
El problema aquí está principalmente en la propia biblioteca React Helmet y, en particular, en que recopila todas las etiquetas dentro del árbol React y la pone de hecho en una variable global, y dado que el código se ha vuelto asíncrono, el código puede mezclar simultáneamente solicitudes procesadas de diferentes usuarios.
La buena noticia aquí es que se hizo una bifurcación sobre la base de esta biblioteca y ahora es mejor dar preferencia a la biblioteca
react-helmet-async . El paradigma principal es que en este caso, el contexto del casco de reacción se aislará en el marco de una sola solicitud encapsulando la aplicación React Tree en HelmetProvider:
import { Helmet, HelmetProvider } from 'react-helmet-async'; app.get('/*', async (req, res) => {
Esto podría terminarse, pero tal vez irás más lejos en un intento de exprimir el máximo rendimiento y mejorar algunas métricas de SEO. Por ejemplo, puede mejorar la métrica Tiempo hasta el primer byte (TTFB), cuando el servidor puede enviar el diseño de página con fragmentos a medida que se calculan, en lugar de esperar hasta que se calcule por completo. Para hacer esto, comenzará a buscar el uso de
renderToNodeStream en lugar de
renderToString .
Aquí nuevamente nos enfrentamos con un pequeño problema. Para obtener todas las metaetiquetas que necesita una página, debemos pasar por todo el árbol de reacción de la aplicación, pero el problema es que las metaetiquetas deben enviarse antes del momento en que comenzamos a transmitir contenido usando renderToNodeStream. De hecho, debemos calcular el React Tree dos veces y se ve así:
app.get('/*', async (req, res) => { const helmetContext = {}; let app = ( <HelmetProvider context={helmetContext}> <App/> </HelmetProvider> ); // do a first pass render so that react-helmet-async // can see what meta tags to render ReactDOMServer.renderToString(app); const { helmet } = helmetContext; response.write(` <html> <head> ${helmet.title.toString()} ${helmet.meta.toString()} </head> <body> `); const stream = ReactDOMServer.renderToNodeStream(app); stream.pipe(response, { end: false }); stream.on('end', () => response.end('</body></html>')); });
Con este enfoque, la necesidad de dicha optimización se convierte en principio en una gran pregunta y es poco probable que mejoremos la métrica TTFB que queremos lograr.
Aquí podemos jugar un poco de optimización y hay varias opciones
- en lugar de renderToString, use renderToStaticMarkup, que probablemente ayudará en cierta medida a ganar algo de tiempo
- en lugar de utilizar los renderizadores que ofrece el react de la caja, cree su propia versión ligera del paso a través del árbol de reacción, por ejemplo, basado en la biblioteca react-tree-walker , o rechace renderizar completamente el árbol y mire solo en el primer nivel del árbol, sin prestar atención a los componentes integrados, por lo que decir representación superficial
- considere un sistema de almacenamiento en caché que a veces puede saltear la primera caminata a través del árbol de reacción
Pero, en cualquier caso, todo lo descrito parece demasiado sofisticado y, en principio, arroja dudas sobre esta carrera por la eficiencia, cuando se construye una arquitectura anormalmente compleja en un par de milisegundos.
Me parece en este caso, para aquellos que están familiarizados con la forma de extraer datos para renderizar para el SSR (y si alguien no lo sabe, entonces este es un excelente
artículo sobre este tema), ayudaremos a extraer la metaetiqueta para la página.
El concepto general es que tenemos un archivo de configuración para enrutadores: esta es una estructura JS ordinaria, que es una matriz de objetos, cada uno de los cuales contiene varios campos del
componente tipo,
ruta . En función de la url de solicitud, encontramos el enrutador y el componente asociado desde el archivo de configuración. Para estos componentes, definimos un conjunto de métodos estáticos como
loadData y, por ejemplo,
creaMetatags para nuestras metaetiquetas.
Por lo tanto, el componente de la página se convertirá así:
class ProductPage extends React.Component { static createMetatags(store, request){ const item = selectItem(store, request.params.product_id); return [] .concat({property: 'og:description', content: item.desc}) .concat({property: 'og:title', content: item.title}) } static loadData(store, request){
Hemos definido un método estático createMetatags que crea el conjunto requerido de metaetiquetas. Con esto en mente, el código en el servidor será así:
app.get('/*', async (req, res) => { const store = createStore(); const matchedRoutes = matchRoutes(routes, request.path);
Es decir Ahora no necesitamos renderizar el árbol React dos veces: podemos extraer inmediatamente todo lo que necesitamos para trabajar desde una aplicación isomorfa, por analogía con la extracción de datos para una ruta.