En el nuevo año, comencemos nuestra conversación con usted con un artículo inicial sobre la representación del lado del servidor. Si está interesado, es posible una publicación más reciente sobre Nuxt.js y más trabajos de publicación en esta dirección.
Con el advenimiento de las bibliotecas y los marcos de trabajo JavaScript modernos, que están destinados principalmente a crear páginas web interactivas y aplicaciones de una sola página, todo el proceso de mostrar páginas al usuario ha cambiado mucho.
Antes de la llegada de las aplicaciones totalmente generadas por JS en el navegador, el HTML se entregaba al cliente en respuesta a una llamada HTTP. Esto se puede hacer devolviendo un archivo HTML estático con el contenido, o procesando la respuesta usando un lenguaje del lado del servidor (PHP, Python o Java), y de una manera más dinámica.
Esta solución le permite crear sitios receptivos que se ejecutan mucho más rápido que los sitios de solicitud-respuesta estándar, ya que elimina el tiempo dedicado a la solicitud "en el camino".
Una respuesta típica del servidor a una solicitud a un sitio escrito en React se vería así:
<!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>
Al seleccionar esta respuesta, nuestro navegador también seleccionará el "paquete" que
app.js
contiene nuestra aplicación, y después de uno o dos segundos, mostrará la página completa.
En este punto, ya puede usar el inspector HTML integrado del navegador para ver todo el HTML renderizado. Sin embargo, mirando el código fuente, no veremos nada más que el HTML anterior.
¿Por qué es esto un problema?
Si bien este comportamiento no será un problema para la mayoría de nuestros usuarios o al desarrollar una aplicación, puede resultar indeseable si:
- -, ,
- , ,
Si, desde un punto de vista demográfico, su público objetivo pertenece a uno de estos grupos, entonces trabajar con el sitio será inconveniente; en particular, los usuarios tendrán que esperar mucho tiempo, mirando el letrero "Cargando ..." (o peor aún, en una pantalla en blanco).
"Está bien, pero demográficamente, mi público objetivo definitivamente no es uno de estos grupos, ¿debería preocuparme?"
Hay otras dos cosas a considerar al trabajar con una aplicación renderizada por el cliente: motores de búsqueda y presencia en las redes sociales .
Hoy en día, de todos los motores de búsqueda, solo Google tiene alguna capacidad para mostrar un sitio y tener en cuenta su JS antes de mostrar una página. Además, aunque Google podrá mostrar la página de índice de su sitio, se sabe que puede haber problemas para navegar por sitios que tienen un enrutador.
Esto significa que será muy difícil para su sitio subir a la cima de los resultados de cualquier motor de búsqueda que no sea Google.
El mismo problema se observa en las redes sociales, por ejemplo, en Facebook: si se comparte un enlace a su sitio, ni su nombre ni la imagen de vista previa se mostrarán correctamente.
Cómo resolver este problema
Hay varias formas de solucionarlo.
A - Intente mantener estáticas todas las páginas clave de su sitio
Cuando se está creando un sitio de plataforma, donde el usuario tendrá que iniciar sesión con su nombre de usuario, y sin iniciar sesión en el sistema, el contenido no se proporciona al visitante, puede intentar dejar páginas públicas estáticas (escritas en HTML) de su sitio, en particular, el índice, "acerca de nosotros", "contactos "Y no use JS cuando los muestre .
Dado que su contenido está limitado por los requisitos de inicio de sesión, los motores de búsqueda no lo indexarán y no podrá compartirlo en las redes sociales.
B - Genere partes de su aplicación como páginas HTML durante la compilación
Puede agregar bibliotecas como react-snapshot a su proyecto ; se utilizan para generar copias HTML de las páginas de su aplicación y almacenarlas en un directorio dedicado. Luego, este directorio se implementa junto con el paquete JS. Por lo tanto, el servidor servirá HTML junto con la respuesta, y su sitio será visto por aquellos usuarios que tengan JavaScript desactivado, así como por los motores de búsqueda, etc.
Por lo general, configurar react-snapshot es fácil: simplemente agregue la biblioteca a su proyecto y modifique el script de compilación de la siguiente manera:
"build": "webpack && react-snapshot --build-dir static"
La desventaja de esta solución es la siguiente: todo el contenido que queremos generar debe estar disponible en el momento de la compilación; no podemos acceder a ninguna API para obtenerlo, tampoco podemos generar previamente contenido que dependa de los datos proporcionados por el usuario. (por ejemplo, de una URL).
C: cree una aplicación JS que utilice la representación del servidor
Uno de los mayores puntos de venta de la generación actual de aplicaciones JS es que se pueden ejecutar tanto en el cliente (navegador) como en el servidor. Esto permite generar HTML para páginas que son más dinámicas, aquellas cuyo contenido aún no se conoce en el momento de la compilación. Estas aplicaciones a menudo se denominan "isomórficas" o "universales".
Las dos soluciones de renderizado del lado del servidor más populares para React son:
- next.js - github.com/zeit/next.js
- Gatsby - github.com/gatsbyjs/gatsby
Cree su propia implementación de SSR
Importante: si va a intentar crear su propia implementación de SSR para las aplicaciones React usted mismo, deberá proporcionar un nodo backend para su servidor. No podrá implementar esta solución en un host estático como lo haría con las páginas de github.
Lo primero que debemos hacer es crear una aplicación, como cualquier otra aplicación de React.
Creemos un punto de entrada:
// index.js
import React from 'react';
import { render } from 'react-dom';
import App from './App.js';render(<App />, document.getElementById('root'));
Y el componente-aplicación (App):
// App.js
import React from 'react';const App = () => {
return (
<div>
Welcome to SSR powered React application!
</div>
);
}
Y también un "contenedor" para cargar nuestra aplicación:
// index.html
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
</head>
<body>
<div id="root"></div>
<script src="/bundle.js"></script>
</body>
</html>
Como puede ver, la aplicación es bastante sencilla. En este artículo, no realizaremos todos los pasos necesarios para generar el ensamblaje correcto de webpack + babel.
Si inicia la aplicación en su estado actual, aparecerá un mensaje de bienvenida en la pantalla. Al mirar la fuente, verá el contenido del archivo
index.html
, pero el mensaje de bienvenida no estará allí. Para resolver este problema, agreguemos la representación del servidor. Primero, agreguemos 3 paquetes:
yarn add express pug babel-node --save-dev
Express es un potente servidor web para nodo, pug es un motor de plantillas que se puede utilizar con express y babel-node es un contenedor para nodo que proporciona transpilación sobre la marcha.
Primero, copiemos nuestro archivo
index.html
y guárdelo como
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>
Como puede ver, el archivo no ha cambiado mucho, excepto por lo que ahora se inserta en el HTML
!{app}
. Esta es una variable
pug
que luego será reemplazada por el HTML real.
Creemos nuestro servidor:
// 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'));
Analicemos este archivo en orden.
import { renderToString } from 'react-dom/server';
La biblioteca react-dom contiene una exportación con nombre separada
renderToString
que funciona como la que conocemos
render
, pero no representa el DOM, sino HTML como una cadena.
const app = express();
app.set('view engine', 'pug');
app.use('/', express.static(path.join(__dirname, 'dist')));
Creamos una nueva instancia de servidor express y le decimos que vamos a utilizar un motor de plantillas
pug
. En este caso, podríamos arreglárnoslas con la lectura habitual del archivo y realizando la operación "buscar y reemplazar", pero este enfoque es realmente ineficaz y puede causar problemas debido al acceso múltiple al sistema de archivos, o problemas con el almacenamiento en caché.
En la última línea, le decimos a express que busque un archivo en el directorio
dist
, y si la solicitud (por ejemplo
/bundle.js
) coincide con el archivo presente en este directorio, devuélvalo.
app.get('*', (req, res) => {
});
Ahora le decimos a express que agregue un controlador a cada URL no coincidente, incluido nuestro archivo inexistente
index.html
(como recordará, lo cambiamos de nombre a
index.pug
, y no está en el directorio
dist
).
const html = renderToString(
<App />
);
Con la ayuda
renderToString
mostramos nuestra aplicación. El código se ve exactamente como el punto de entrada, pero esta coincidencia es opcional.
res.render(path.join(__dirname, 'src/index.pug'), {
app: html
});
Ahora que hemos renderizado HTML, le decimos a express que renderice el archivo en respuesta
index.pug
y reemplace la variable
app
con el HTML que recibimos.
app.listen(3000, () => console.log('listening on port 3000'));
Finalmente, nos aseguramos de que el servidor se inicie y lo configuramos para que escuche en el puerto 3000.
Ahora solo necesitamos agregar el script requerido a
package.json
:
"scripts": {
"server": "babel-node server.js"
}
Ahora, al llamar
yarn run server
, deberíamos recibir la confirmación de que el servidor se está ejecutando. Vaya a localhost : 3000 en el navegador , donde, nuevamente, deberíamos ver nuestra aplicación. Si miramos el código fuente en esta etapa, vemos:
<!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>
Si todo se ve así, significa que la renderización del servidor está funcionando como se esperaba, ¡y puede comenzar a extender su aplicación!
¿Por qué todavía necesitamos bundle.js?
En el caso de una aplicación tan simple, que se considera aquí, no es necesario incluir bundle.js; sin este archivo, nuestra aplicación seguirá funcionando. Pero en el caso de una aplicación real, aún debe incluir este archivo.
Esto permitirá que los navegadores que pueden manejar JavaScript se hagan cargo del trabajo y luego interactúen con su página ya en el lado del cliente, y aquellos que no saben cómo analizar JS irán a la página con el HTML deseado que el servidor devolvió.
Cosas para recordar
A pesar de que la representación del servidor parece bastante sencilla, al desarrollar aplicaciones, debe prestar atención a algunos temas que a primera vista no son del todo obvios:
- , , . , , HTML,
this.state
, componentDidMount
— , , . , , . , (res.render
) , . -- react (. @reach/router react-router) , URL, . !