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

Clase 093 — Las cuatro estrategias de renderizado#

⬅️ Parte 6 · 📚 Parte 7 · 🎓 Clases · 094 ➡️ Parte 7 — Renderizado y full-stack · Nivel 🟡 intermedio · Pista fullstack (Renderizado y full-stack) ✅ Clase construida — 5 implementaciones verificadas contra contrato.json.

🏗️ Primera clase con metaframeworks. Next.js, Nuxt, SvelteKit, Remix y Astro se construyen de verdad —build y servidor de producción— antes de que el contrato les pregunte nada.

🎯 Objetivo#

¿Dónde se genera el HTML? Hay cuatro respuestas, y la correcta cambia por pantalla, no por proyecto.

Y una demostración que no se ve en ninguna comparativa: la diferencia entre generar al construir y generar por petición no se nota mirando una respuesta. Las dos traen el mismo contenido. Se nota pidiendo dos veces.

📚 Resultados de aprendizaje#

Al terminar podrás:

🧩 La situación#

Una lista de tres tareas. La misma en las tres pantallas — a propósito: si el contenido cambiara, la comparación mediría el contenido en lugar de la estrategia.

Lo único que las distingue es cuándo se generó el HTML que las contiene. Y eso no se ve: hay que provocarlo.

🧮 El contrato#

# Petición Qué comprueba
1 GET /estatico el contenido y su sello — y guarda el sello
2 GET /estatico otra vez el sello es el mismo: se generó una vez
3 GET /servidor el mismo contenido — y guarda su sello
4 GET /servidor otra vez el sello cambió: se genera en cada petición
5 GET /cliente el HTML llega sin el contenido
6 GET /estrategias.json las cuatro, con sus compromisos

Los casos 2 y 4 son la clase entera, y son la razón de que este contrato use una capacidad que casi ninguna otra clase necesita: capturar un valor de una respuesta con guardar_cuerpo y compararlo contra la siguiente.

      "guardar_cuerpo": { "selloEstatico": { "patron": "data-sello=\"([^\"]+)\"" } }

Con una sola petición, las dos pantallas son indistinguibles. Con dos, la diferencia es evidente y no admite discusión.

<!-- generado: fichas -->

📖 Las palabras que esta clase define#

Si alguna de estas no te dice nada todavía, esta es la clase donde se aprende. Las definiciones viven en el glosario, que reúne las del programa entero.

Palabra Qué significa
Renderizado en el servidor (SSR) Generar el HTML en el servidor en cada petición. La página se ve antes y el servidor trabaja más; el JavaScript llega después para darle vida.
Generación estática (SSG) Generar el HTML una vez, al construir, y servirlo como archivo. Lo más rápido y lo más barato, y solo vale si el contenido no depende de quién mira.

🧰 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
Nuxt vue-metaframework de JavaScript/TypeScript (TypeScript) 2016 MIT proyecto independiente
SvelteKit svelte-metaframework de JavaScript/TypeScript (TypeScript) 2022 MIT proyecto independiente
Remix react-metaframework de JavaScript/TypeScript (TypeScript) 2021 MIT proyecto independiente
Astro web-metaframework de JavaScript/TypeScript (TypeScript) 2021 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/cliente/page.js código JavaScript
app/datos.js código JavaScript
app/estatico/page.js código JavaScript
app/estrategias.json/route.js código JavaScript
app/layout.js código JavaScript
app/servidor/page.js código JavaScript
app/tareas.json/route.js código JavaScript
ejecutar.json la receta que usa el verificador: qué hace falta, cómo se prepara y cómo arranca

🔧 Nuxt#

El equivalente de Next.js sobre Vue, con un motor de servidor propio reutilizable fuera del framework.

Preparar sus dependencias, dentro de su directorio:

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

Arrancarla suelta, sin el verificador:

PORT=3000 node .output/server/index.mjs

Qué hay dentro de su directorio:

Archivo Qué es
datos.ts código TypeScript
ejecutar.json la receta que usa el verificador: qué hace falta, cómo se prepara y cómo arranca
nuxt.config.ts código TypeScript
package.json manifiesto de Node.js: nombre, tipo de módulo y dependencias con su rango de versión
pages/cliente.vue archivo del proyecto
pages/estatico.vue archivo del proyecto
pages/servidor.vue archivo del proyecto
pnpm-lock.yaml archivo de bloqueo: la versión exacta de cada dependencia y de sus dependencias

🔧 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/datos.js código JavaScript
src/routes/cliente/+page.server.js código JavaScript
src/routes/cliente/+page.svelte componente de Svelte

🔧 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/datos.js código JavaScript
app/root.jsx componente en JSX
app/routes/cliente.jsx componente en JSX
app/routes/estatico.jsx componente en JSX
app/routes/estrategias[.]json.js código JavaScript
app/routes/servidor.jsx componente en JSX
app/routes/tareas[.]json.js código JavaScript
ejecutar.json la receta que usa el verificador: qué hace falta, cómo se prepara y cómo arranca

🔧 Astro#

Arquitectura de islas: por omisión no envía JavaScript y cada componente interactivo se declara explícitamente. Permite mezclar React, Vue y Svelte en la misma página, lo que lo hace un banco de pruebas ideal para comparar.

Preparar sus dependencias, dentro de su directorio:

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

Arrancarla suelta, sin el verificador:

PORT=3000 node ./dist/server/entry.mjs

Qué hay dentro de su directorio:

Archivo Qué es
astro.config.mjs código JavaScript (módulo 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/datos.mjs código JavaScript (módulo ES)
src/pages/cliente.astro archivo del proyecto
src/pages/estatico.astro archivo del proyecto

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#

Las cinco comparten el mismo contenido y el mismo sello — astro/src/datos.mjs:

 * En la estática se calcula una vez, al construir, y queda escrito en el archivo
 * para siempre. En la de servidor se calcula en cada petición. El contrato pide
 * cada pantalla dos veces y compara: mismo sello significa generada al
 * construir; sello distinto, generada ahora.
 *
 * Es la única forma de demostrar la diferencia, porque el contenido de las dos
 * respuestas es idéntico.

Astro · astro/src/pages/estatico.astro#

La decisión, una línea por página:

export const prerender = true;
// Esta línea significa: genera el HTML AL CONSTRUIR y sírvelo como un archivo.
// El servidor no ejecuta nada — solo entrega bytes.

Y la de al lado — servidor.astro:

// `prerender = false` saca esta página del lote estático y la pone en el
// servidor: su HTML se genera EN CADA PETICIÓN.
//
// Una línea, una página. Esa granularidad es la respuesta de Astro a la pregunta
// de esta parte: la estrategia no se elige por proyecto, se elige por pantalla.

Y una postura que se declara en la configuraciónastro.config.mjs:

 * `output: "static"` es el valor por omisión de Astro, y es una postura: **por
 * omisión no hay servidor**. Las páginas se generan al construir y se sirven
 * como archivos.

Next.js · nextjs/app/servidor/page.js#

export const dynamic = "force-dynamic";

Con un aviso que conviene tener presente al auditar un proyecto ajeno:

 * En Next hay además una vía indirecta que sorprende a mucha gente: usar una
 * función que lee la petición —cookies, cabeceras— **convierte la ruta en
 * dinámica sola**, sin declararlo. Es cómodo y hace difícil saber qué estrategia
 * tiene cada pantalla sin construir el proyecto.

Y la frontera más explícita del elenconextjs/app/cliente/page.js:

 * Esa directiva en la primera línea marca dónde acaba el servidor y empieza el
 * navegador. Todo lo que se importe desde aquí hacia abajo **viaja al cliente**.

La salida de next build lo dice con símbolos: ○ (Static) para cuatro rutas y ƒ (Dynamic) para /servidor. El propio constructor publica la estrategia de cada pantalla, que es lo que a los otros cuatro les cuesta más enseñar.

SvelteKit · sveltekit/src/routes/estatico/+page.server.js#

 * SvelteKit la ejecuta AL CONSTRUIR, guarda el HTML resultante y en producción
 * lo sirve como un archivo. `load` no se vuelve a ejecutar nunca.

Y el componente, que es el mismo para las dos estrategias+page.svelte:

  // Recibe los datos por `data`, venga de donde venga. No sabe si su `load`
  // corrió al construir o hace un milisegundo — y esa ignorancia es lo que
  // permite cambiar de estrategia sin tocar la interfaz.

Esa es la propiedad que hace útil todo esto: cambiar de estrategia no significa reescribir la pantalla.

Con una decisión de arquitectura escondida en una línea — svelte.config.js:

 * SvelteKit no supone un destino: `adapter-node` produce un servidor de Node,
 * `adapter-static` produce archivos, y hay adaptadores para las plataformas de
 * despliegue. El mismo código fuente sale de una forma o de otra según cuál se
 * ponga aquí.

Nuxt · nuxt/nuxt.config.ts — todo en una tabla#

 * Nuxt es el único de los cinco que reúne las decisiones de renderizado **en un
 * solo sitio**: `routeRules` es un mapa de patrón de ruta a estrategia.
 *
 * Tiene una ventaja concreta sobre escribirlo en cada página: se puede leer la
 * arquitectura de la aplicación entera de un vistazo, sin abrir veinte archivos.
 * Y una desventaja simétrica: la decisión queda lejos de la pantalla a la que
 * afecta, así que es fácil que se desincronicen.
  routeRules: {
    "/estatico": { prerender: true },
    "/cliente": { prerender: true },
    "/tareas.json": { prerender: true },
    "/estrategias.json": { prerender: true },

Y el efecto en la página, dicho sin adornos — nuxt/pages/estatico.vue:

// Nuxt la ejecuta al construir y guarda el HTML. En el archivo no hay ninguna
// marca de que sea estática — hay que ir a la tabla. Ese es el precio de tener
// las decisiones juntas.

Remix · remix/app/routes/estatico.jsx — el que dice que no#

Es el único de los cinco sin modo estático, y no por falta de tiempo — remix/vite.config.js:

 * No es una carencia por hacer. Su argumento es que lo estático es un caso
 * particular de lo dinámico con una caché delante, y que esa caché la resuelve
 * mejor una red de distribución con cabeceras HTTP —`Cache-Control`— que el
 * framework con un modo aparte.

Y la implementación declara exactamente lo que hace y lo que no:

 * Pero no es lo mismo, y la diferencia importa: aquí el servidor SÍ trabaja en
 * cada petición —renderiza el componente— y lo único constante es el dato. En
 * los otros cuatro, el servidor no ejecuta nada porque el HTML ya existe.
export function headers() {
  // La cabecera que en un despliegue real haría el trabajo de lo estático.
  return { "Cache-Control": "public, max-age=3600" };
}

Y la postura se nota también en lo que cuesta cada camino — remix/app/routes/cliente.jsx:

 * Es la manera más clara de ver la postura del framework: **el camino cómodo es
 * el del servidor**, y hacerlo en el cliente cuesta más código, no menos.

🔬 Comparación#

Dónde se declara Por omisión ¿Tiene modo estático?
Astro export const prerender en la página estático
Next.js export const dynamic en la ruta estático
SvelteKit export const prerender en +page.server.js servidor
Nuxt routeRules, una tabla central servidor
Remix no se declara servidor por decisión

Cuatro lecturas:

⚠️ Errores frecuentes#

✅ Verificación#

node scripts/run-class.mjs 093

Las cinco se instalan y se construyen antes de responder. Es la clase más lenta del programa hasta aquí, y esa lentitud es parte de lo que se aprende: un metaframework tiene un paso de construcción, y ese paso es lo que permite que haya páginas generadas antes de la primera visita.

Para verlo tú:

curl -s http://127.0.0.1:4100/estatico | grep -o 'data-sello="[^"]*"'

Ejecútalo dos veces contra /estatico y dos contra /servidor.

🧪 Reto de transferencia#

  1. Averigua la estrategia de tu pantalla más visitada. Pídela dos veces y compara algo que cambie —una fecha, un identificador—. Si no cambia, es estática.
  2. Busca una pantalla estática que no debería serlo, o al revés. El síntoma de la primera es contenido viejo; el de la segunda, un servidor trabajando para devolver siempre lo mismo.
  3. Construye la implementación de Next y lee la tabla de la salida. Los símbolos y ƒ son el mapa de estrategias de la aplicación entera.

🔗 Enlaces#

Fuentes#