TLDR: Vamos a generar un HTML como primera respuesta al «request» del cliente, pero vamos a responder no con un archivo HTML, sino con pedacitos del mismo, con Streams.
En este post vamos a usar el término TTI (Time to interactive) que es una métrica de «progreso» que mide el performance del sitio web. En otras palabras: el tiempo que le tomará a un sitio web en entregar la data al cliente y que el cliente pueda interactuar con la página.
Y vamos a reducir al máximo ese TTI evitando la entrega de un archivo y en su lugar, justo como lo hacemos en este otro post con PDFs, pero en este caso con componentes de React. En el momento en que el cliente comience a recibir los pedacitos de datos, podrá, casi de inmediato, renderizar el contenido.
Vamos, pues, a explorar nuestra curiosidad, creando lo que se conoce como:
STREAMING SERVER-SIDE RENDERING
Como te imaginarás, esto es posible gracias a los componentes de servidor de React. En el caso de los strems a dos herramientas específicas:
renderToPipeableStream
renderToNodeStream
ya está deprecadorenderToStaticNodeStream
En ese video, te muestro cómo usar el método renderToString
para crear un SSR. Pero en esta ocasión no queremos trabajar con strings
, sino con streams
. Así que toca usar: renderToPipeableStream
En un SSR típico, hay que generar todo el HTML y entregarlo completo, para luego ser leído y conseguir los enlaces y el bundle.
Veamos cómo sería con streams:
Cómo puedes notar, el tiempo de respuesta del servidor se reduce al máximo, pues en vez de entregar todo un archivo, el servidor puede devolver el primer pedacito, con el que el cliente puede comenzar a renderizar. Para luego seguir recibiendo pedacitos que continúa renderizando así, como le van llegando.
Esta es toda la app de express:
// index.js
import React from "react";
import path from "path";
import express from "express";
import { renderToPipeableStream } from "react-dom/server";
import App from "../components/App.jsx";
const PORT = 3000;
const app = express();
app.get("/favicon.ico", (req, res) => res.end());
app.use("client.js", (req, res) => res.redirect("/public/client.js"));
app.use(express.static(path.resolve(__dirname, "public")));
app.use("/build", express.static(path.resolve(__dirname, "public")));
app.get("/", (req, res) => {
const { pipe } = renderToPipeableStream(
<div id="root">
<App />
</div>,
{
bootstrapScripts: ["/client.js"],
onShellReady() {
res.setHeader("content-type", "text/html");
res.write("<html><body><h1>Blissmo streaming</h1>");
pipe(res);
},
}
);
});
app.listen(PORT);
console.log(`Running on: http://localhost:${PORT}`);
Como te puedes dar cuenta en el código; el componente <App/>
es enviado como streams gracias al método renderToPipeableStream
. El HTML inicial es enviado al objeto de respuesta junto con los pedacitos de datos del componente <App/>
.
Todos estos datos contienen información útil que nuestra app debe usar para poder renderizar el contenido correctamente, hablamos de datos como el <title></title>
y las hojas de estilo <link/>
. Si usáramos el método renderToString
tendríamos que esperar hasta que la aplicación reciba todos los datos antes de comenzar la carga, procesar los metadatos y posteriormente hidratar.
No olvidemos llamar a hydrateRoot
en nuestro client.js
(Revisa el repo con todo el proyecto)
Con esto es posible que nuestra <App/>
comience la carga y el procesamiento de información de inmediato, con la posibilidad de seguir recibiendo los pedacitos de datos del componente <App/>
¿fascinante no crees?
Además, el servidor utiliza mucha menos memoria y se mantiene mucho más responsivo, evitando casi por completo el bloqueo I/O
por lo que es famoso Node.js y sus streams.
renderToPipeableStream
está disponible desde la versión 16 de React (2016) pero hasta ahora está tomando mucha importancia, pues el desarrollo web está volviendo al servidor.
Otra ventaja que quiero mencionar aquí es que los streams pueden ser leídos por los crawlers
. Esto significa que el SEO será interpretado perfectamente.
¿Qué tal?, ahora que entiendes cómo funcionan los Streams de React, ¿Te gustaría saber más sobre los experimentos que está haciendo el equipo de React con SSR?
Déjame saberlo en mi twitter.
Abrazo. Bliss.