Lección 5 de 915 min
Estrategias de renderizado
CSR, SSR, SSG, ISR, streaming, hidratación parcial y selectiva, islas y resumabilidad.
Abres la ficha de un producto en el móvil. En una tienda ves una pantalla en blanco durante dos segundos; en otra, el contenido aparece al instante, pero el botón «Añadir al carrito» no responde hasta un rato después. Detrás de esas dos experiencias hay una decisión: dónde y cuándo se genera el HTML, y cuánto JavaScript hace falta para que la página cobre vida.
No lo confundas con la lección del módulo 1 sobre cómo el navegador pinta una página: allí veías qué hace el navegador con el HTML que recibe. Aquí la pregunta es anterior: quién produce ese HTML (el navegador, el servidor en cada visita o el build) y en qué momento.
¿Dónde y cuándo se genera el HTML?
Las cinco primeras estrategias se diferencian en el momento en que se construye el HTML: en el navegador, en el servidor con cada visita, una sola vez al publicar o en algún punto intermedio.
CSR (renderizado en el cliente)
El servidor envía un HTML casi vacío y JavaScript construye la página en el navegador.
- HTML se genera:
- En el navegador
- Típico de:
- SPA con React, Angular o Vue
Con CSR (Client-Side Rendering), el servidor entrega un HTML mínimo con un <div> vacío y un enlace a un paquete de JavaScript. El navegador lo descarga, lo ejecuta, pide los datos a una API y solo entonces pinta el contenido. Es el modelo clásico de las SPA (Single Page Applications).
Tras la primera carga, la navegación es muy fluida: cambiar de pantalla no recarga la página, solo pide datos. Además, el HTML es estático, así que se sirve desde una CDN sin servidor que mantener.
Úsalo cuando
En aplicaciones detrás de un inicio de sesión, como paneles, herramientas internas o editores, donde el SEO no importa y el usuario pasa mucho rato dentro.
Ten en cuenta
La primera carga es la más lenta, sobre todo en móviles modestos: no se ve nada útil hasta que el JavaScript se descarga y se ejecuta. Los buscadores y las vistas previas al compartir un enlace pueden no ver el contenido.
// index.html solo trae <div id="app"></div> y este script
const app = document.querySelector("#app")!;
app.textContent = "Cargando…";
// Hasta aquí, el usuario no ha visto ni un producto
const productos: Producto[] = await fetch("/api/productos").then((r) => r.json());
app.replaceChildren(
...productos.map((p) => {
const tarjeta = document.createElement("article");
tarjeta.textContent = `${p.nombre}: ${p.precio} €`;
return tarjeta;
}),
);SSR (renderizado en el servidor)
El servidor genera el HTML completo en cada petición, con los datos ya dentro.
- HTML se genera:
- En cada petición
Con SSR (Server-Side Rendering), cada visita hace que el servidor consulte los datos, ejecute los componentes y devuelva un HTML completo. El navegador puede pintarlo en cuanto llega, sin esperar a ningún JavaScript, y los buscadores ven el contenido real.
Después llega el JavaScript del framework y hidrata la página para que responda a los clics, como verás en el siguiente grupo. Así funcionan Next.js, Nuxt, SvelteKit o Angular con @angular/ssr. En el fondo es la vuelta, ahora con componentes, de lo que PHP o Rails hacían desde siempre.
Úsalo cuando
Cuando el contenido cambia con cada petición o depende del usuario (resultados de búsqueda, un muro personalizado) y además importan el SEO o la primera impresión.
Ten en cuenta
El servidor trabaja en cada visita: si una consulta tarda, el usuario espera con la pantalla en blanco porque crece el TTFB (Time To First Byte, el tiempo hasta el primer byte). Necesitas servidores que escalen con el tráfico. Y hasta que termina la hidratación, la página se ve pero no responde.
app.get("/producto/:id", async (req, res) => {
// Esta consulta se repite en cada visita
const producto = await db.productos.buscar(req.params.id);
res.send(`<!doctype html>
<h1>${escapar(producto.nombre)}</h1>
<p>${producto.precio} €</p>
<button id="comprar">Añadir al carrito</button>
<script type="module" src="/app.js"></script>`);
});SSG (generación estática)
Todas las páginas se generan una vez, en el build, y se sirven como archivos.
- HTML se genera:
- En el build
- Herramientas:
- Astro, Next.js, Hugo, Eleventy
Con SSG (Static Site Generation), el HTML de cada página se genera al construir el proyecto. Después, el servidor o la CDN solo entregan archivos ya hechos, así que la respuesta es rapidísima, barata y casi imposible de tumbar con tráfico.
Esta misma app usa SSG: cada lección se prerenderiza en el build con generateStaticParams de Next.js, porque el contenido solo cambia cuando se publica una versión nueva. Las partes interactivas, como el simulador de esta lección, se hidratan en el navegador igual que con SSR.
Úsalo cuando
Para contenido igual para todos que cambia al publicar: documentación, blogs, cursos, páginas de marketing, catálogos pequeños.
Ten en cuenta
Cualquier cambio exige un build nuevo, y con decenas de miles de páginas el build puede tardar mucho. Tampoco sirve para contenido personalizado: el HTML es el mismo para todos los visitantes.
// En el build, Next.js genera una página por cada slug
export async function generateStaticParams() {
const lecciones = await obtenerLecciones();
return lecciones.map((leccion) => ({ slug: leccion.slug }));
}
export default async function Pagina({ params }: { params: Promise<{ slug: string }> }) {
const { slug } = await params;
const leccion = await obtenerLeccion(slug);
return <h1>{leccion.titulo}</h1>;
}ISR (regeneración estática incremental)
Páginas estáticas que se regeneran en segundo plano cada cierto tiempo o a demanda.
- HTML se genera:
- En el build o al pedirlo, y se renueva
- Popularizado por:
- Next.js
ISR (Incremental Static Regeneration) combina SSG y SSR. Las páginas se sirven desde una caché como si fueran estáticas, pero cada una tiene una caducidad: la primera visita que llega después de caducar recibe la versión guardada al instante y, en segundo plano, el servidor genera una nueva para los siguientes. También se puede pedir la regeneración a demanda, por ejemplo cuando el CMS avisa de un cambio.
Además, no hace falta generar todas las páginas en el build: las que nadie ha visitado se generan la primera vez que alguien las pide y luego se guardan. El término lo popularizó Next.js, y otros frameworks y plataformas ofrecen mecanismos equivalentes.
Úsalo cuando
En sitios grandes cuyo contenido cambia de vez en cuando, como una tienda con miles de productos o un periódico, donde un build completo por cada cambio sería inviable.
Ten en cuenta
Algunos visitantes verán contenido algo desactualizado hasta que termine la regeneración. Y depende de la caché de la plataforma de alojamiento, así que no funciona igual en todas partes.
// Configuración clásica de Next.js (sin Cache Components):
// se sirve desde la caché y, como mucho una vez por minuto,
// se regenera en segundo plano con datos nuevos
export const revalidate = 60;
export default async function Producto({ params }: { params: Promise<{ id: string }> }) {
const { id } = await params;
const producto = await obtenerProducto(id);
return <h1>{producto.nombre}: {producto.precio} €</h1>;
}Streaming SSR
El servidor envía el HTML por trozos: primero el esqueleto, después lo que tarda más.
- HTML se genera:
- En cada petición, por partes
Con SSR clásico, una sola consulta lenta retrasa toda la página. Con streaming, el servidor envía cuanto antes lo que ya tiene (cabecera, menú, estructura) con marcadores de posición para lo que falta, y mantiene la conexión abierta. Cuando cada parte lenta termina, la envía en un nuevo trozo de HTML que ocupa su hueco.
El usuario ve algo útil mucho antes, y el navegador empieza a descargar el JavaScript mientras el servidor sigue trabajando. En React, cada parte lenta se marca con <Suspense>; Next.js, SvelteKit o SolidStart integran el streaming.
Úsalo cuando
En páginas con partes rápidas y partes lentas: una ficha de producto cuyo precio llega enseguida pero cuyas reseñas o recomendaciones dependen de servicios más lentos.
Ten en cuenta
El código de estado HTTP y las cabeceras viajan con el primer trozo: si después algo falla, ya no puedes responder con un 404 o una redirección normal. Algunos proxies acumulan la respuesta entera y anulan el streaming.
import { Suspense } from "react";
export default function Producto() {
return (
<main>
<Ficha /> {/* rápida: va en el primer trozo */}
<Suspense fallback={<p>Cargando reseñas…</p>}>
<Resenas /> {/* lenta: llega después, en otro trozo */}
</Suspense>
</main>
);
}Esta app es SSG
Las lecciones de este curso se generan en el build: cuando abres una, recibes un HTML ya hecho y el servidor no tiene que consultar nada. Solo las partes interactivas, como los simuladores y el cuestionario, traen JavaScript que se hidrata en tu navegador.
Hidratar menos, o nada
SSR, SSG, ISR y streaming comparten un coste: después de pintar el HTML, hay que descargar y ejecutar JavaScript para que la página responda. Las técnicas de este grupo buscan reducir ese trabajo.
Hidratación
El paso en que el JavaScript del framework «despierta» un HTML generado en el servidor.
El HTML que llega del servidor se ve bien, pero está «seco»: los botones no tienen manejadores de eventos y el framework no conoce el estado. La hidratación consiste en descargar el JavaScript de los componentes, volver a ejecutarlos en el navegador y enganchar sus eventos y su estado al HTML que ya existe, sin volver a crearlo.
Mientras tanto, la página parece lista pero no responde: es el llamado valle inquietante (uncanny valley). Cuanto más JavaScript tiene la página y más lento es el dispositivo, más dura. Si el HTML del servidor y lo que genera el cliente no coinciden, el framework avisa de un error de hidratación.
Úsalo cuando
Siempre que uses SSR o SSG con un framework de componentes: es lo que hace interactiva la página. No es algo que se elija, sino un coste que conviene entender.
Ten en cuenta
Usar Date.now(), Math.random() o window al renderizar provoca diferencias entre servidor y cliente. Por eso los simuladores de este curso solo los usan dentro de manejadores de eventos.
import { hydrateRoot } from "react-dom/client";
import { App } from "./App";
// No crea el DOM de nuevo: recorre el HTML que envió el servidor
// y le engancha los eventos y el estado de cada componente
hydrateRoot(document.getElementById("root")!, <App />);Hidratación parcial
Hidratar solo los componentes interactivos y dejar el resto como HTML estático.
En una página típica, la mayor parte del contenido (textos, imágenes, el pie) nunca cambia ni responde a nada. La hidratación parcial envía JavaScript solo para los componentes que lo necesitan, como un buscador o un carrito, y deja el resto como HTML puro, sin coste.
Hay varias formas de lograrlo. Las islas, que verás enseguida, son la más conocida. Los React Server Components van en la misma dirección: los componentes de servidor no envían su código al navegador. Y Angular, en sus versiones recientes, ofrece hidratación incremental con bloques @defer, que retrasan la hidratación de una parte hasta que hace falta.
Úsalo cuando
En páginas con mucho contenido y pocas zonas interactivas: blogs, fichas de producto, documentación, páginas de aterrizaje.
Ten en cuenta
Obliga a pensar qué es interactivo y qué no al diseñar los componentes. Y el estado que comparten varias zonas interactivas es más difícil de gestionar.
<app-ficha [producto]="producto" />
<!-- Se pinta en el servidor, pero no se hidrata
hasta que el usuario llega a él con el scroll -->
@defer (hydrate on viewport) {
<app-resenas [productoId]="producto.id" />
}Hidratación selectiva
Hidratar la página por partes, empezando por la que el usuario está usando.
La hidratación selectiva, que React introdujo junto al streaming, divide la hidratación en trozos independientes, uno por cada <Suspense>. No hace falta esperar a que llegue todo el HTML ni todo el JavaScript: cada parte se hidrata en cuanto tiene lo necesario.
Además, prioriza: si el usuario hace clic en una zona que aún no está hidratada, React la hidrata antes que las demás y después atiende el clic. Se sigue enviando todo el JavaScript, a diferencia de la hidratación parcial, pero el navegador ya no queda bloqueado en una única tarea larga.
Úsalo cuando
En aplicaciones de React con streaming SSR, como las del App Router de Next.js, donde obtienes este comportamiento al usar <Suspense>.
Ten en cuenta
No reduce el JavaScript total: en un móvil lento, la página entera seguirá tardando en estar lista. Es un reparto más inteligente del trabajo, no menos trabajo.
<Layout>
<Suspense fallback={<MenuEsqueleto />}>
<Menu />
</Suspense>
<Suspense fallback={<ComentariosEsqueleto />}>
<Comentarios />
</Suspense>
</Layout>
// Cada <Suspense> se hidrata por separado. Si el usuario
// pulsa en los comentarios, React los hidrata primero.Arquitectura de islas
HTML estático con «islas» interactivas independientes, cada una con su JavaScript.
- Ejemplos:
- Astro, Fresh
La arquitectura de islas lleva la hidratación parcial al extremo: por defecto todo es HTML sin JavaScript, y cada componente interactivo es una isla que se hidrata por su cuenta, sin depender de las demás. Un artículo de blog puede tener una sola isla, el botón de suscripción, y ningún JavaScript en todo lo demás.
Astro es el framework más conocido que la usa: decides cuándo se hidrata cada isla con directivas como client:load (al cargar), client:idle (cuando el navegador está libre) o client:visible (al aparecer en pantalla). Cada isla puede incluso usar un framework distinto: React, Vue, Svelte…
Úsalo cuando
En sitios centrados en el contenido (blogs, documentación, marketing, tiendas) que solo necesitan interactividad en algunos puntos.
Ten en cuenta
Encaja peor en aplicaciones muy interactivas, donde casi todo sería una isla y comparten mucho estado. Comunicar dos islas entre sí requiere un mecanismo externo, como un almacén de estado compartido.
---
import Ficha from "../components/Ficha.astro";
import Carrito from "../components/Carrito.tsx";
import Resenas from "../components/Resenas.tsx";
---
<Ficha /> <!-- HTML puro: sin JavaScript -->
<Carrito client:load /> <!-- isla: se hidrata al cargar -->
<Resenas client:visible /> <!-- isla: solo al llegar con el scroll -->Resumabilidad
Reanudar en el navegador lo que dejó el servidor, sin volver a ejecutar componentes.
- Framework de referencia:
- Qwik
La hidratación repite en el navegador trabajo que el servidor ya hizo: ejecutar los componentes para saber qué eventos enganchar. La resumabilidad (resumability) lo evita: el servidor serializa en el HTML todo lo necesario (el estado y qué manejador corresponde a cada evento) y el navegador simplemente reanuda desde ahí.
Qwik es el framework que la popularizó. Al cargar, solo ejecuta un script diminuto que escucha los eventos de toda la página; cuando haces clic en un botón, descarga en ese momento el código de ese manejador concreto y lo ejecuta. El JavaScript inicial es mínimo, sea cual sea el tamaño de la página.
Úsalo cuando
En sitios grandes donde el tiempo hasta que la página responde es crítico, como el comercio electrónico en móviles, y el equipo está dispuesto a adoptar un modelo de programación distinto.
Ten en cuenta
La primera interacción con cada parte puede notar la descarga de su código si no se precarga bien. Y el ecosistema es mucho más pequeño que el de React o Angular.
// El $ marca los puntos por los que Qwik puede partir el código:
// el manejador del clic solo se descarga cuando alguien pulsa
export const Contador = component$(() => {
const cuenta = useSignal(0);
return <button onClick$={() => cuenta.value++}>Pulsado {cuenta.value} veces</button>;
});Pruébalo
En el simulador, elige «Móvil lento» y compara SSG con Islas: el contenido aparece a la vez, pero mira cuánto tarda cada uno en responder. Después prueba CSR y SSR, cambia a «Rápido» y fíjate en cómo se reducen las diferencias.
Interactivo
Línea de tiempo del renderizado
Elige una estrategia y una red. El modelo es simplificado, pero las proporciones son las que importan: qué ocurre primero y cuánto espera el usuario.
Perfil: móvil de gama media. La escala es la misma para todas las estrategias de este perfil, para que puedas compararlas.
TTFB · 205 ms
En blanco
Primer contenido · 766 ms
Esqueleto, sin datos
- Comprar
LCP · 1,0 s
Se ve y responde
- Comprar
Interactivo · 1,0 s
Se ve y responde
El servidor responde enseguida con un HTML vacío, pero no se ve nada útil hasta que el JavaScript se descarga, se ejecuta y pide los datos a la API. A cambio, cuando el contenido aparece ya responde, y las pantallas siguientes irán muy rápidas.
Con este perfil, el usuario mira una pantalla vacía o un esqueleto hasta los 1,0 s. No hay espera entre ver y usar, pero se ve tarde.
Interactivo
Estrategias de renderizado cara a cara
Activa o desactiva opciones para compararlas cara a cara.
| Característica | CSR | SSR | SSG |
|---|---|---|---|
| Cuándo se genera el HTML | En el navegador, tras descargar el JavaScript | En cada petición | Una vez, en el build |
| TTFB | Muy bajo (HTML casi vacío) | Más alto: espera al servidor | Muy bajo (un archivo) |
| Frescura de los datos | Al día: los datos se piden al cargar | Al día | La del último build |
| JavaScript enviado | Toda la aplicación | Todo, para hidratar | Todo lo interactivo, para hidratar |
| Frameworks típicos | SPA con React, Angular o Vue | Next.js, Nuxt, SvelteKit, Angular SSR | Astro, Next.js, Hugo, Eleventy |
Ponlo en práctica
Saber qué es cada cosa es la mitad del trabajo. La otra mitad es reconocer cuándo usarla.
Interactivo
¿Qué usarías?
Lee cada situación y elige la opción que mejor la resuelve. Fallar aquí no cuenta: es para practicar.
La documentación de una biblioteca: cientos de páginas iguales para todos que cambian con cada versión publicada.
Para recordar
- La gran pregunta es cuándo se genera el HTML: en el navegador (CSR), en cada petición (SSR y streaming), en el build (SSG) o en el build con renovación (ISR).
- Generar el HTML en el servidor o en el build hace que el contenido se vea antes, pero la hidratación sigue costando JavaScript: hay un rato en que la página se ve y no responde.
- La hidratación parcial y las islas envían JavaScript solo para lo interactivo; la selectiva lo reparte por prioridades; la resumabilidad evita volver a ejecutar los componentes.
- No hay una estrategia ganadora, y muchos frameworks permiten mezclarlas página a página. Esta app, por ejemplo, prerenderiza sus lecciones con SSG.
Comprueba lo aprendido
Cuestionario
Responde las 5 preguntas para completar la lección.
1.¿Por qué una página con SSR puede tener un TTFB peor que la misma página con SSG?
2.Una página con SSR se ve completa, pero el botón «Comprar» no responde durante dos segundos. ¿Qué está pasando?
3.¿Qué diferencia una página con islas de una SPA hidratada entera?
4.Con ISR y una caducidad de 60 segundos, llega una visita cinco minutos después de la última regeneración. ¿Qué recibe?
5.¿Qué hace la resumabilidad de Qwik en lugar de hidratar?