Saltar al contenido
Framework Ecosystems LabsUn contrato, muchos ecosistemas, la misma prueba.

Clase 100 — HTML en flujo#

⬅️ Clase 099 · 📚 Parte 7 · 🎓 Clases · 101 ➡️ Parte 7 — Renderizado y full-stack · Nivel 🔴 avanzado · Pista fullstack (Renderizado y full-stack) ✅ Clase construida — 3 implementaciones verificadas contra contrato.json.

👥 Tres implementaciones, y el elenco es corto a propósito. Astro y Nuxt saben enviar respuestas parciales, pero ninguno de los dos tiene una forma declarativa de decir «esta parte de la pantalla que llegue después». Sin eso, la comparación sería entre tres frameworks que resuelven el problema y dos que lo dejan en manos de quien escribe, y esa no es la comparación de esta clase.

🎯 Objetivo#

La clase 099 arregló la cascada juntando peticiones. Queda la que no se puede juntar: una parte de la pantalla es lenta y punto.

La respuesta es no esperarla. Mandar el documento con lo que ya se sabe, dejar un hueco marcado, y enviar el contenido del hueco por el mismo canal cuando esté. Una respuesta HTTP, dos tandas.

📚 Resultados de aprendizaje#

Al terminar podrás:

🧩 La situación#

Una pantalla con dos partes. La cabecera se sabe al instante —el nombre está en la sesión— y la lista tarda trescientos milisegundos.

Cada implementación la sirve dos veces: en /flujo, aplazando la lista; en /sin-flujo, esperándola. El HTML final es el mismo. Lo que cambia es cuándo llega cada mitad.

🧮 El contrato#

# Petición Qué comprueba
1 GET /flujo la cabecera y, al final, el dato de la lista
2 GET /flujo y el texto de espera que ocupó el hueco
3 GET /sin-flujo lo mismo sin texto de espera: nunca hubo hueco
4 GET /flujo.json la cabecera llega antes, y sin flujo no
5 GET /flujo.json los cronómetros marcaron algo en las dos pantallas
6 GET /flujo.json cómo se pide el flujo y quién pinta la parte aplazada

Este contrato no puede medir el flujo por sí mismo, y el motivo enseña algo:

 * Y ahí está el motivo de que esta clase necesite su propio medidor en lugar de
 * usar el contrato tal cual: `await respuesta.text()` espera a que la respuesta
 * termine. Con eso, una respuesta que llegó en dos tandas y una que llegó de
 * golpe son indistinguibles — que es exactamente lo que esta clase quiere
 * distinguir.

Y las peticiones del contrato llevan una cabecera que no lleva ninguna otra clase:

        "cabeceras": { "user-agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36" }

Sin ella, Remix devuelve el documento entero y el caso 2 falla. No es un capricho del ejercicio: es una decisión deliberada del framework, y está contada abajo.

<!-- generado: fichas -->

🧰 Las piezas de esta clase, una por una#

Antes del código: qué es cada framework, qué versión se está usando y qué hace falta para ejecutarlo. Todo lo de esta sección sale de los archivos reales del repositorio —el catálogo, la receta de arranque y el manifiesto de dependencias de cada ecosistema—, así que no puede quedarse desactualizado sin que la validación lo detecte.

Framework Qué es Desde Licencia Quién lo mantiene
Next.js react-metaframework de JavaScript/TypeScript (TypeScript) 2016 MIT Vercel
Remix react-metaframework de JavaScript/TypeScript (TypeScript) 2021 MIT proyecto independiente
SvelteKit svelte-metaframework de JavaScript/TypeScript (TypeScript) 2022 MIT proyecto independiente

🔧 Next.js#

Convirtió el renderizado en servidor en la opción por omisión del ecosistema React. Su acoplamiento con una plataforma concreta es la dimensión que el módulo 11 obliga a puntuar.

Preparar sus dependencias, dentro de su directorio:

pnpm install --silent --ignore-scripts
pnpm exec next build

Arrancarla suelta, sin el verificador:

PORT=3000 pnpm exec next start -p 3000

Qué hay dentro de su directorio:

Archivo Qué es
app/Lista.jsx componente en JSX
app/flujo.json/route.js código JavaScript
app/flujo/page.js código JavaScript
app/fuente.js código JavaScript
app/layout.js código JavaScript
app/medicion.js código JavaScript
app/sin-flujo/page.js código JavaScript
ejecutar.json la receta que usa el verificador: qué hace falta, cómo se prepara y cómo arranca

🔧 Remix#

Apostó por los estándares de la plataforma web —formularios, respuestas, caché— frente a abstracciones propias. Su fusión con React Router es un ejemplo de convergencia entre proyectos.

Preparar sus dependencias, dentro de su directorio:

pnpm install --silent --ignore-scripts
pnpm exec remix vite:build

Arrancarla suelta, sin el verificador:

PORT=3000 pnpm exec remix-serve ./build/server/index.js

Qué hay dentro de su directorio:

Archivo Qué es
app/fuente.js código JavaScript
app/medicion.js código JavaScript
app/root.jsx componente en JSX
app/routes/flujo.jsx componente en JSX
app/routes/flujo[.]json.js código JavaScript
app/routes/sin-flujo.jsx componente en JSX
ejecutar.json la receta que usa el verificador: qué hace falta, cómo se prepara y cómo arranca
package.json manifiesto de Node.js: nombre, tipo de módulo y dependencias con su rango de versión

🔧 SvelteKit#

Enrutado por sistema de archivos y adaptadores de despliegue intercambiables, que es una estrategia de salida incorporada al diseño.

Preparar sus dependencias, dentro de su directorio:

pnpm install --silent --ignore-scripts
pnpm exec vite build

Arrancarla suelta, sin el verificador:

PORT=3000 node build/index.js

Qué hay dentro de su directorio:

Archivo Qué es
ejecutar.json la receta que usa el verificador: qué hace falta, cómo se prepara y cómo arranca
package.json manifiesto de Node.js: nombre, tipo de módulo y dependencias con su rango de versión
pnpm-lock.yaml archivo de bloqueo: la versión exacta de cada dependencia y de sus dependencias
pnpm-workspace.yaml raíz de instalación propia, y la prohibición de ejecutar scripts al instalar
src/app.html plantilla o marcado
src/lib/fuente.js código JavaScript
src/lib/medicion.js código JavaScript
src/routes/flujo.json/+server.js código JavaScript

Si alguna cadena de herramientas no está en tu máquina, node scripts/doctor.mjs dice cuál falta y con qué comando se instala. No hace falta tenerlas todas: el verificador ejecuta lo que encuentra y declara lo que omitió.

<!-- fin generado: fichas -->

🌐 Las implementaciones — el código a la vista#

La fuente y el medidor, idénticos en los tres#

nextjs/app/fuente.js:

 * Sin flujo, la respuesta entera espera a la lista: la pantalla está en blanco
 * trescientos milisegundos aunque la mitad estuviera lista desde el principio.
 * Con flujo, la cabecera sale ya y la lista llega cuando llega.

Y el medidor, que lee a trozos — nextjs/app/medicion.js:

 * Así que se lee el cuerpo con un lector, trozo a trozo, y se anota el momento
 * en que aparece cada marca. Es lo mismo que hace un navegador, y es la única
 * forma de ver el flujo desde fuera.

Con la marca que descubrió la diferencia de fondo:

 * `lista` busca el DATO —el texto de la primera tarea— porque el dato llega en
 * los tres frameworks, aunque no de la misma forma. `listaEnHtml` busca el
 * marcado, y ahí es donde se separan: hay quien manda el HTML ya construido y
 * quien manda solo el dato para que el navegador lo pinte. Los dos son flujo;
 * solo uno funciona sin JavaScript.

Y el hallazgo que más se aprovecha fuera de aquí:

 * Remix decide si envía la respuesta en flujo o entera mirando esta cabecera:
 * a un rastreador le manda el documento completo, porque un buscador que lea
 * media página indexa media página. Sin cabecera, `isbot` da por hecho que quien
 * pide no es un navegador, y el flujo se apaga.
 * Se descubre midiendo: con esta cabecera la cabecera llega a los treinta
 * milisegundos, sin ella a los trescientos sesenta. Es una decisión sensata del
 * framework y un aviso para cualquiera que mida rendimiento con una herramienta
 * de línea de órdenes: **puede que no estés midiendo lo que ve un navegador**.

Next.js · una etiqueta alrededor de lo lento#

nextjs/app/flujo/page.js:

 * Sin ella, Next espera a que el árbol entero esté resuelto y manda el documento
 * completo. Con ella, manda todo lo que ya tiene —incluida la cabecera y el
 * texto de espera— y deja un hueco marcado; cuando `Lista` termina, manda un
 * segundo trozo con el contenido y una instrucción para colocarlo en su sitio.
 * Es la misma pantalla, la misma consulta y el mismo total. Lo que cambia es
 * cuándo se ve la primera mitad, y eso es lo que mide la persona que espera.

Y el requisito de la parte lenta — app/Lista.jsx:

/** La parte lenta, en su propio componente. Que sea `async` y que esté aparte es
 *  todo lo que Next necesita para poder aplazarla. */

SvelteKit · el await que no se escribe#

sveltekit/src/routes/flujo/+page.server.js:

 * Lo que se devuelve resuelto —`nombre`— viaja en el primer trozo. Lo que se
 * devuelve como promesa —`tareas`— viaja después, cuando se resuelve, y
 * SvelteKit se encarga de coserlo.
 * Es la declaración más discreta de las tres: no hay componente que envolver ni
 * etiqueta que añadir, solo un `await` que no se escribe. Y ahí está su riesgo,
 * que conviene decir: **quitar o poner ese `await` cambia el comportamiento de
 * la pantalla sin que se note al leer**.

Y la otra mitad, en la plantilla — +page.svelte:

    `{#await}` es la otra mitad: mientras la promesa no se resuelve se pinta lo
    de en medio, y cuando llega se sustituye. Es el equivalente exacto de
    `<Suspense>`, escrito como una estructura de control del lenguaje de
    plantillas en lugar de como un componente.

Remix · se declara igual que en SvelteKit y no hace lo mismo#

remix/app/routes/flujo.jsx:

 * Con `v3_singleFetch`, una promesa devuelta por el `loader` no se espera: viaja
 * el resto de la respuesta y ella llega después. La declaración es la misma que
 * en SvelteKit —un `await` que no se escribe— y la forma de consumirla es la de
 * React: `<Suspense>` con `<Await>` dentro.
 * Es un buen ejemplo de por qué comparar frameworks por su sintaxis engaña. La
 * decisión —qué se aplaza— se escribe igual en los dos; lo que cambia es quién
 * pinta la parte aplazada, y eso no se ve en el código.

🔬 Comparación#

Medido leyendo la respuesta a trozos, en la misma máquina:

cabecera lista separación sin flujo, la cabecera ¿la lista llega como HTML?
Next.js 9 ms 306 ms 297 ms 309 ms
SvelteKit 2 ms 302 ms 300 ms 303 ms solo el dato
Remix 5 ms 304 ms 299 ms 309 ms

Y cómo se pide en cada uno:

Cómo se declara Quién pinta la parte aplazada
Next.js <Suspense fallback={…}> alrededor del componente lento el servidor: manda el HTML ya construido
SvelteKit no poniendo el await en load el navegador: el servidor manda el dato
Remix no poniendo el await en el loader el servidor, con <Await> dentro de <Suspense>

Cuatro lecturas:

⚠️ Errores frecuentes#

✅ Verificación#

node scripts/run-class.mjs 100

Para verlo tú, con cualquiera arrancada, la forma más directa —-N desactiva el almacenamiento intermedio de curl, así que el HTML aparece en pantalla en dos tandas separadas por trescientos milisegundos:

curl -N -A "Mozilla/5.0 Chrome/120" http://127.0.0.1:4100/flujo

🧪 Reto de transferencia#

  1. Encuentra tu parte lenta. En tu pantalla más pesada, mira qué consulta marca el ritmo. Si es una sola y las demás son rápidas, esta clase es tuya.
  2. Aplázala y mide. Con el medidor de aquí: leer a trozos y anotar cuándo aparece cada marca. El total no va a bajar; el primer pintado sí.
  3. Comprueba qué manda tu framework. Apaga el JavaScript y pide la pantalla aplazada. Si el hueco no se rellena, tu framework manda el dato y no el marcado, y ya sabes con qué contar.

🔗 Enlaces#

Fuentes#