Renderizado del lado del servidor, o SSR, es una frase que se oye a menudo en la comunidad de desarrollo de frontend.
En el nivel más básico, el renderizado del lado del servidor es exactamente lo que describe: renderizado de aplicaciones en el servidor. Navegas a un sitio web, hace una solicitud al servidor, renderiza algo de HTML y obtienes el resultado completamente renderizado en tu navegador. Bastante sencillo. Puede que te estés preguntando por qué la comunidad tiene una palabra de moda para esto.
Antes del surgimiento de aplicaciones web ricas y dinámicas que dependían en gran medida de JavaScript y jQuery, esencialmente todas las aplicaciones web se renderizaban en servidor. PHP, WordPress e incluso sitios HTML básicos son ejemplos de esto.
Cuando visitas una página en uno de estos sitios, recuperas todos los datos HTML y todo. Si hace clic en un enlace, el navegador realizará otra solicitud al servidor. Después de la respuesta, el navegador se actualizará y mostrará la siguiente página desde cero. Este enfoque funciona bien, y lo ha hecho durante años; los navegadores son espectacularmente rápidos en la representación de HTML estático. ¿Qué ha cambiado?
Desde el cambio de siglo, el uso de JavaScript ha pasado de un poco de espacio aquí y allá para la interactividad de la página web al lenguaje indiscutible de elección en la web. Estamos enviando constantemente más lógica y JavaScript al navegador.
Los marcos de una sola página como React y Vue han dado paso a esta nueva era de aplicaciones web renderizadas por clientes dinámicas, complejas y basadas en datos. Estos SPAs difieren de las aplicaciones renderizadas por servidor porque no obtienen contenido completamente renderizado con datos del servidor antes de renderizarlo en la pantalla.
Las aplicaciones renderizadas del lado del cliente renderizan su contenido en el navegador mediante JavaScript. En lugar de buscar todo el contenido del servidor, simplemente obtienen una página HTML sin contenido corporal y renderizan todo el contenido dentro usando JavaScript.
El beneficio de esto es que evita las actualizaciones de página completas que ocurren con aplicaciones completamente renderizadas por servidor, que pueden ser un poco discordantes para el usuario. Las aplicaciones renderizadas por cliente de una sola página actualizarán el contenido de su pantalla, obtendrán datos de las API y se actualizarán justo frente a usted sin ningún tipo de actualización de página. Esta característica es lo que hace que las aplicaciones web modernas se sientan rápidas y «nativas» al interactuar con ellas.
- Compensaciones de renderizado del lado del cliente
- SEO
- Rendimiento de carga inicial
- Lo mejor de ambos mundos
- Tutorial: La implementación de SSR
- Nuestras herramientas
- Diagrama de arquitectura
- Creación de nuestra aplicación
- Configuración del proyecto
- Renderizar la aplicación en Lambda
- Alojamiento y toques finales
- Ejecutar nuestra aplicación
- A dónde ir desde aquí
- Concurrencia aprovisionada
- Versiones Lambda
- Aplicación web de pila completa
- Conclusión
- Visibilidad completa de las aplicaciones de producción de React
Compensaciones de renderizado del lado del cliente
No todo es sol y arco iris en el mundo de SPA renderizado del lado del cliente. Hay algunas compensaciones que vienen con la representación de su aplicación en el lado del cliente. Dos ejemplos principales son el SEO y el rendimiento de carga inicial.
SEO
Dado que las aplicaciones renderizadas por el cliente devuelven una página HTML básica con muy poco contenido antes de que se inicie JavaScript y renderice el resto, puede ser difícil para los rastreadores de motores de búsqueda comprender la estructura HTML de su página, lo que es perjudicial para los rankings de búsqueda de su sitio. Google ha hecho un gran trabajo en torno a esto, pero aún así se recomienda evitar la representación del lado del cliente si el SEO es particularmente importante.
Rendimiento de carga inicial
Con las aplicaciones renderizadas por cliente, por lo general, las siguientes cosas suceden cuando abre la página por primera vez:
- La aplicación carga algo de HTML básico, como un shell de aplicación o barra de navegación estática
- Verá un indicador de carga de algún tipo
- Su contenido se renderiza
El problema con esto es que su aplicación no mostrará nada hasta que el JavaScript se cargue completamente desde la red y haya terminado de renderizar los elementos en su pantalla.
En pocas palabras, el problema con el rendimiento del lado del cliente en general es que no puede controlar en qué dispositivo cliente alguien usa su aplicación, ya sea su teléfono inteligente de última generación, una potente máquina de escritorio de gama alta o un teléfono inteligente de gama baja de 1 100.
Nosotros, sin embargo, controlamos el servidor. Casi siempre podemos darle a nuestro servidor más CPU y memoria y ajustarlo para que funcione mejor para nuestros usuarios.
Lo mejor de ambos mundos
Podemos tener lo mejor de ambos mundos cuando usamos renderizado del lado del servidor con tecnologías de frontend modernas. La forma en que esto generalmente funciona es que el servidor renderiza y devuelve la aplicación completamente renderizada en la primera carga. El siguiente paso, conocido como hidratación, es donde se descargará y ejecutará su paquete de JavaScript. Esto conecta controladores de eventos y conecta cosas como su enrutador del lado del cliente.
Con este enfoque, obtiene todos los beneficios de SSR en la carga inicial, luego cada interacción a partir de ese momento será manejada por JavaScript del lado del cliente. Esto proporciona una carga inicial rápida y amigable para el SEO, seguida de la experiencia de aplicación web dinámica de una sola página que conocemos y amamos.
Las aplicaciones de este tipo se conocen como aplicaciones universales porque el mismo JavaScript se ejecuta en el cliente y el servidor. También puede escuchar el término más elegante «isomorfo» que se usa, que significa exactamente lo mismo.
Tutorial: La implementación de SSR
SSR tampoco está exenta de ventajas y desventajas. Agrega sobrecarga a su desarrollo al introducir una configuración más compleja, así como al tener que alojar y administrar su propio servidor. Estos problemas son la razón por la que los marcos de trabajo increíbles como Next.js y Razzle son muy populares: abstraen la parte de configuración de SSR y le permiten concentrarse en escribir código de interfaz de usuario.
En este tutorial, no vamos a usar ningún framework SSR. La mejor manera de aprender cómo funciona algo es realmente construyéndolo, por lo que vamos a aprender cómo crear la configuración SSR más simple que podamos que proporcione:
- CDN global
- API de backend completamente funcional
- Sin servidores ni infraestructura para administrar
- Implementación de un solo comando
Vamos a implementar una aplicación React renderizada en servidor universal creada con create-react-app en Amazon Web Services (AWS). No necesita tener experiencia con AWS para seguir adelante.
Nuestras herramientas
Para crear nuestra aplicación, vamos a hacer uso de algunos servicios de AWS diferentes.
- AWS Amplify: Un marco de trabajo de alto nivel para administrar servicios de AWS, principalmente para el desarrollo móvil y web
- AWS Lambda: Ejecute código en la nube sin administrar servidores
- AWS Cloudfront (CDN): Una red de distribución de contenido responsable de entregar y almacenar en caché contenido en todo el mundo
- AWS Simple Storage Service (S3): Donde almacenaremos nuestros activos estáticos (JS, CSS, etc.)
Diagrama de arquitectura
Nuestra función Lambda es responsable de la representación de servidor de nuestra aplicación React. Usaremos S3 para almacenar nuestro contenido estático y Cloudfront CDN para servirlo. No necesita tener ningún conocimiento previo de estos servicios, ya que AWS Amplify nos facilitará enormemente su creación.
Creación de nuestra aplicación
En primer lugar, debe instalar la CLI de AWS Amplify y crear una cuenta de AWS si aún no tiene una. Puede hacerlo siguiendo esta breve guía.
Configuración del proyecto
Ahora que Amplify está configurado, podemos comenzar a configurar nuestro proyecto React. Vamos a usar la fantástica aplicación create-react para ayudarnos. Asumiendo que tienes Nodo.js y npm instalados, podemos ejecutar:
npx create-react-app amplify-ssrcd amplify-ssr yarn add aws-amplify amplify init
Seleccione las opciones predeterminadas en el asistente AWS Amplify.
Nuestro proyecto React ahora está arrancado con Amplify y listo para que agreguemos nuestro» servidor » para SSR. Hacemos esto ejecutando amplify add api
y respondiendo algunas preguntas:
$ amplify add api? Please select from one of the below mentioned services: REST? Provide a friendly name for your resource to be used as a label for this category in the project: amplifyssr? Provide a path (e.g., /items): /ssr? Choose a Lambda source: Create a new Lambda function? Provide a friendly name for your resource to be used as a label for this category in the project: amplifyssr? Provide the AWS Lambda function name: ssr? Choose the function runtime that you want to use: NodeJS? Choose the function template that you want to use: Serverless expressJS function? Do you want to access other resources created in this project from your Lambda function? N? Do you want to edit the local lambda function now? N? Restrict API access: N? Do you want to add another path? N
Esto creará las plantillas, directorios y código relevantes necesarios para nuestra infraestructura y backend de AWS: una función AWS Lambda que ejecutará un pequeño servidor Express responsable de renderizar nuestra aplicación React.
Antes de implementar nuestra infraestructura, hay algunos cambios que necesitamos hacer dentro de nuestra aplicación React para prepararla para el renderizado del lado del servidor. Abra src/App.js
(el componente principal de la aplicación para su aplicación React) y pegue lo siguiente:
import React from 'react';function App() { return ( <div className="App"> <header className="App-header"> Server Rendered React App </header> </div> );}export default App;
A continuación, necesitamos crear un script que renderice nuestra aplicación React en el lado del servidor. Esto se hace con la función renderToString
en el paquete react-dom/server
. Esta función es responsable de tomar nuestro componente <App />
y renderizarlo en el lado del servidor como una cadena, lista para ser devuelta como HTML completamente renderizado al cliente.
Crear un archivo en src/render.js
con el siguiente código:
import React from "react";import { renderToString } from "react-dom/server";import App from "./App";export default () => renderToString(<App />);
Excelente: nuestra aplicación React del lado del cliente tiene todo el código que necesita ser renderizado en el lado del servidor. Esto significa que ahora debemos codificar el punto final del lado del servidor que renderizará nuestra aplicación React.
Tenemos un problema, sin embargo, necesitamos que la función src/render
y nuestro código de componente <App />
se ejecuten en el lado del servidor. El servidor no sabe nada de React ni de los módulos ES de forma predeterminada. Por esta razón, vamos a transpilar el código de la aplicación React usando Babel al lado del servidor.
Para hacer esto, instalemos algunas dependencias de Babel en nuestro proyecto.
yarn add --dev @babel/core @babel/cli @babel/preset-react @babel/preset-env
A continuación, cree un .babelrc
en la raíz de su proyecto. Este archivo se utiliza para configurar Babel y decirle qué plugins/presets usar.
{ "presets":}
Finalmente, vamos a actualizar nuestro package.json
para transpilar nuestro código como parte del paso de compilación. Esto transpondrá los archivos en el directorio amplify/backend/function/amplifyssr/src/client
, que es donde almacenaremos todo el JavaScript universal que necesita ejecutarse en el lado del cliente, así como en el servidor para SSR.
"scripts": { "start": "react-scripts start", "transpile": "babel src --out-dir amplify/backend/function/amplifyssr/src/client --copy-files", "build": "npm run transpile && react-scripts build && npm run copy", "copy": "cp build/index.html amplify/backend/function/amplifyssr/src/client", "test": "react-scripts test", "eject": "react-scripts eject" },
Renderizar la aplicación en Lambda
¡La configuración de compilación se ha completado! Vayamos a amplify/backend/function/amplifyssr/src
e instalemos react
y react-dom
, ya que ambos serán necesarios para que Lambda realice SSR.
yarn add react react-dom
Ahora para configurar nuestro servidor Express, que se ejecutará en Lambda. La función Lambda se generó automáticamente cuando completamos el paso amplify add api
antes y elegimos una API REST
y ExpressJS
.
Amplify ya ha configurado el servidor Express para que se ejecute en Lambda, por lo que todo lo que necesitamos hacer ahora es agregar un punto final al servidor: renderizar nuestra aplicación React cuando alguien llegue a la URL de la API en el navegador. Actualice su archivo amplify/backend/function/amplifyssr/src/app.js
para que contenga el siguiente código:
/* Amplify Params - DO NOT EDIT ENV REGIONAmplify Params - DO NOT EDIT */const express = require('express')const bodyParser = require('body-parser')const awsServerlessExpressMiddleware = require('aws-serverless-express/middleware')const fs = require('fs');const render = require('./client/render').default;// declare a new express appconst app = express()app.use(bodyParser.json())app.use(awsServerlessExpressMiddleware.eventContext())// Enable CORS for all methodsapp.use(function(req, res, next) { res.header("Access-Control-Allow-Origin", "*") res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept") next()});app.get('*', function(req, res) { // Read the index.html file from the create-react-app build const html = fs.readFileSync("./client/index.html", "utf-8"); // Server side render the react application const markup = render(); // Replace the empty body of index.html with the fully server rendered react application and send it back to the client res.send(html.replace(`<div></div>`, `<div>${markup}</div>`))});module.exports = app
Nuestro servidor Express ahora está listo para SSR, y podemos implementar nuestra aplicación React.
Alojamiento y toques finales
Una vez que recibamos el HTML renderizado por el servidor del renderizado inicial de nuestra aplicación, recuperaremos el paquete JavaScript del lado del cliente para que se haga cargo desde allí y nos proporcione un SPA completamente interactivo.
Necesitamos un lugar para alojar nuestros archivos JavaScript y estáticos del lado del cliente. En AWS, el servicio generalmente utilizado para esto es S3 (Simple Storage Service), un almacén de objetos en la nube escalable de forma masiva.
También vamos a poner una CDN delante de ella para el almacenamiento en caché global y el rendimiento. Con Amplify, podemos crear estos dos recursos para nuestro proyecto ejecutando algunos comandos desde el directorio raíz de nuestro proyecto:
$ amplify add hostingSelect the plugin module to execute Amazon CloudFront and S3? Select the environment setup: PROD (S3 with CloudFront using HTTPS)? hosting bucket name (name your bucket or use the default)
Ahora puede implementar toda su infraestructura, incluida la función Lambda de Express server, el bucket S3 y la CDN, ejecutando el comando amplify publish
.
La salida de la consola mostrará todos los recursos relevantes de las plantillas que Amplify está creando para usted. Tenga en cuenta que la creación de una CDN de Cloudfront puede llevar un tiempo, así que tenga paciencia. Una vez creados los recursos, la URL de la CDN de Cloudfront se mostrará en el terminal.
Publish started for S3AndCloudFront✔ Uploaded files successfully.Your app is published successfully.https://d3gdcgc9a6lz30.cloudfront.net
Lo último que tenemos que hacer es indicarle a React de dónde obtener nuestro paquete del lado del cliente después de que el servidor renderice la aplicación. Esto se hace en create-react-app usando la variable de entorno PUBLIC_URL
. Actualicemos de nuevo nuestros scripts de la aplicación React package.json
para que se vean como los siguientes:
"scripts": { "start": "react-scripts start", "transpile": "babel src --out-dir amplify/backend/function/amplifyssr/src/client --copy-files", "build": "npm run transpile && PUBLIC_URL=<your-cloudfront-url> react-scripts build && npm run copy", "copy": "cp build/index.html amplify/backend/function/amplifyssr/src/client", "test": "react-scripts test", "eject": "react-scripts eject" },
Reconstruya e implemente su aplicación en AWS con esta configuración actualizada.
amplify publish
¡Ahora deberíamos tener una aplicación React completamente renderizada del lado del servidor que se ejecute en AWS!
Ejecutar nuestra aplicación
La URL de la API de SSR se puede encontrar en amplify/backend/amplify-meta.json
. Busque RootUrl
en su archivo JSON y debería ver la URL en la que puede visitar su nueva aplicación renderizada por servidor. Debería tener un aspecto similar al siguiente:
"output": { "ApiName": "amplifyssr", "RootUrl": "https://g6nfj3bvsg.execute-api.eu-west-1.amazonaws.com/dev", "ApiId": "g6nfj3bvsg"},
Visite la URL de la puerta de enlace de API en su navegador en <your-api-url>/ssr
y debería ver su nueva y brillante aplicación React renderizada en servidor. Si se sumerge en la pestaña de Red en el navegador de su elección y ve las solicitudes, notará que la solicitud a /ssr
tiene una respuesta HTML completamente renderizada con nuestra aplicación React renderizada dentro del <body>
del documento.
<div> <div class="App" data-reactroot=""> <header class="App-header">Server Rendered React App</header> </div></div>
También notará las solicitudes que se realizan a su URL de Cloudfront desde el navegador para cargar el JavaScript del lado del cliente que se encargará del renderizado desde aquí, lo que nos brinda lo mejor de los mundos de renderizado del lado del cliente y del lado del servidor.
A dónde ir desde aquí
Este tutorial está destinado a ponerlo en marcha con el renderizado del lado del servidor lo más rápido posible sin preocuparse por administrar infraestructura, CDN y más. Después de haber utilizado el enfoque sin servidor, hay algunas mejoras agradables que podemos hacer a nuestra configuración.
Concurrencia aprovisionada
Una forma en que AWS Lambda puede seguir siendo extremadamente económica es que las funciones de Lambda que no se han alcanzado en un tiempo se quedarán «inactivas».»Esto significa esencialmente que cuando los ejecutemos de nuevo, habrá lo que se conoce como un «arranque en frío», un retraso de inicialización que debe ocurrir antes de que el Lambda responda.
Después de esto, la lambda vuelve a estar «caliente» durante un período de tiempo y responderá a las solicitudes posteriores rápidamente hasta el siguiente período de inactividad prolongado. Esto puede causar tiempos de respuesta poco fiables.
A pesar de ser «sin servidor», Lambda utiliza contenedores ligeros para procesar cualquier solicitud. Cada contenedor solo puede procesar una solicitud en un momento dado. Además del problema de arranque en frío después de un período de inactividad, lo mismo ocurre cuando muchas solicitudes simultáneas se ejecutan en la misma función Lambda, lo que provoca que se inicien en frío más contenedores o trabajadores simultáneos antes de responder.
En el pasado, muchos ingenieros han resuelto este problema escribiendo scripts para hacer ping al Lambda periódicamente para mantenerlo caliente. Ahora hay una forma nativa de AWS mucho mejor de resolver esto, y se conoce como Concurrencia aprovisionada.
Con la concurrencia aprovisionada, puede solicitar muy fácilmente un número determinado de contenedores dedicados para que se mantengan calientes para una función Lambda específica. Esto le dará un tiempo de respuesta SSR mucho más consistente en tiempos de carga alta y esporádica.
Versiones Lambda
Puede crear varias versiones Lambda para sus funciones y dividir el tráfico entre ellas. Esto es muy potente en nuestra aplicación SSR, ya que nos permite realizar actualizaciones en el lado Lambda y probarlas A/B con una porción más pequeña de usuarios.
Puede publicar varias versiones de su Lambda y dividir el tráfico entre ellas en pesos que especifique. Por ejemplo, es posible que desee renderizar un banner de CTA para algunos usuarios para medir la interacción, pero no renderizarlo para otros. Puede hacer esto con las versiones Lambda.
Aplicación web de pila completa
Como se explicó anteriormente, AWS Amplify ya crea una API REST y un servidor Express para nosotros, en los que hemos creado un punto final para renderizar nuestra aplicación React. Siempre podemos agregar más código y puntos finales a este servidor Express en amplify/backend/function/amplifyssr/src/app.js
, lo que nos permite convertir nuestra aplicación en una aplicación web de pila completa, con base de datos, autenticación y más.
Puede utilizar el fantástico conjunto de herramientas Amplify de AWS para crear estos recursos o conectarlos a su propia infraestructura, incluso si no está alojada en AWS. Puede tratar su backend de AWS Lambda como cualquier otro servidor Express y construir sobre él.
Ya tiene configurada toda la canalización de implementación ejecutando amplify publish
para que pueda centrarse en escribir código. El punto de partida de este tutorial te da total flexibilidad para hacer lo que quieras a partir de aquí.
Conclusión
El renderizado del lado del servidor no tiene que ser difícil. Podemos usar herramientas totalmente gestionadas como Next o Razzle, que son increíbles por derecho propio, pero para muchos equipos esto puede ser un cambio de paradigma demasiado grande dado su código o requisitos existentes. El uso de un enfoque personalizado, sencillo y de bajo mantenimiento puede hacer la vida más fácil, especialmente si ya está utilizando AWS o Amplify para su proyecto.
SSR puede agregar un montón de valor a sus aplicaciones web y proporcionar un rendimiento muy necesario o un impulso de SEO. Somos afortunados en la comunidad de desarrollo web por tener herramientas que pueden crear CDN, backends sin servidor y aplicaciones web completamente alojadas con unos pocos comandos o clics.
Incluso si cree que no necesita SSR, es un tema muy frecuente y común en el ecosistema JavaScript. Tener una comprensión de sus beneficios y compensaciones será útil para casi cualquier persona involucrada en la esfera del desarrollo web.
Espero que hayas aprendido algo hoy – ¡gracias por leer! No dude en ponerse en contacto conmigo o seguirme en Twitter, donde twitteo y blogueo sobre JavaScript, Python, AWS, automatización y desarrollo sin código.
Visibilidad completa de las aplicaciones de producción de React
Depurar las aplicaciones de React puede ser difícil, especialmente cuando los usuarios experimentan problemas que son difíciles de reproducir. Si está interesado en monitorear y rastrear el estado de Redux, detectar automáticamente los errores de JavaScript y rastrear las solicitudes de red lentas y el tiempo de carga de los componentes, pruebe LogRocket.
LogRocket es como un DVR para aplicaciones web, que graba literalmente todo lo que sucede en tu aplicación React. En lugar de adivinar por qué ocurren los problemas, puede agregar e informar sobre el estado en el que se encontraba su solicitud cuando se produjo un problema. LogRocket también supervisa el rendimiento de la aplicación, creando informes con métricas como la carga de la CPU del cliente, el uso de la memoria del cliente y más.
El paquete de middleware LogRocket Redux añade una capa adicional de visibilidad a sus sesiones de usuario. LogRocket registra todas las acciones y el estado de sus tiendas Redux.
Modernice la forma en que depura sus aplicaciones React: comience a monitorear de forma gratuita.