wandres.dev
RENDIMIENTO · Core Web Vitals

Rendimiento de SSR: caché, streaming y el edge

Cuando el HTML se genera por petición, el reloj empieza en el servidor: qué es el TTFB y qué lo domina, cómo cachear datos y respuestas en el nivel correcto con s-maxage y stale-while-revalidate, cómo aprovechar el streaming de HTML colocando bien los await, y cómo acercar el cómputo al visitante con server islands y el edge. Medir y afinar el rendimiento del renderizado en servidor.

⏱ 17 min

En un sitio estático el HTML ya existe cuando llega la petición; en SSR se fabrica en ese instante, y el reloj del rendimiento arranca en el servidor. Ahora hay un tiempo nuevo que gobernar —el que tarda tu código en producir el primer byte— y sobre él se apoya todo lo demás: por rápido que sea el navegador, no puede pintar lo que aún no ha recibido. Afinar SSR es aprender a mover el trabajo en el tiempo y en el espacio: adelantarlo con caché, aplazarlo con streaming, acercarlo con el edge. La latencia deja de ser un coste fijo y se vuelve algo que se programa.

🎯 Al terminar esta lección sabrás
  • Medir el TTFB y entender qué lo domina en el renderizado por petición.
  • Cachear datos y respuestas en el nivel correcto con las cabeceras adecuadas.
  • Aprovechar el streaming de HTML colocando bien los await.
  • Acercar el cómputo al visitante con server islands y el edge.

TTFB: la primera cifra del SSR

El TTFB —Time To First Byte— mide cuánto tarda el servidor en enviar el primer byte de la respuesta desde que recibe la petición. En un sitio estático es minúsculo; en SSR lo domina el trabajo que haces antes de emitir HTML: consultar bases de datos, llamar a APIs, renderizar. Un TTFB alto empuja hacia atrás todo lo demás, incluido el LCP, porque el navegador no puede empezar hasta que llega ese primer byte.

# Medir el TTFB de una URL sin descargar el cuerpo entero
curl -w "TTFB: %{time_starttransfer}s\n" -o /dev/null -s https://mi-sitio.dev

La causa más común de un TTFB inflado es una espera lenta en el bloque de la página —una consulta que tarda— colocada en el camino crítico. La disciplina del SSR consiste en atacar esa espera por tres vías: no repetirla (caché), no bloquear con ella toda la página (streaming) o hacerla desde más cerca (edge).

Cachear datos y respuestas

Hay dos niveles de caché y conviene no confundirlos. El primero es cachear los datos: evitar repetir una consulta cara guardando su resultado con un tiempo de vida. El segundo es cachear la respuesta entera en un CDN, para que muchas peticiones idénticas ni siquiera lleguen a ejecutar tu código.

// Cachear datos en memoria con un tiempo de vida
const cache = new Map<string, { valor: unknown; hasta: number }>();

async function cachear(clave: string, ttl: number, cargar: () => Promise<unknown>) {
  const hit = cache.get(clave);
  if (hit && hit.hasta > Date.now()) return hit.valor;
  const valor = await cargar();
  cache.set(clave, { valor, hasta: Date.now() + ttl });
  return valor;
}

Para cachear la respuesta, la cabecera Cache-Control con directivas de CDN hace el trabajo. s-maxage fija cuánto la sirve cacheada el CDN, y stale-while-revalidate permite entregar una versión ligeramente vieja al instante mientras se refresca en segundo plano: el visitante nunca espera a la revalidación.

// El CDN sirve cacheado 60 s y revalida sin bloquear durante 10 min
context.response.headers.set(
  'Cache-Control',
  'public, s-maxage=60, stale-while-revalidate=600',
);
💡
El mejor SSR es el que no se ejecuta dos veces

Una página SSR que cambia poco no necesita renderizarse en cada visita. Con s-maxage, la primera petición paga el coste completo y las siguientes se sirven desde el CDN casi gratis, hasta que el tiempo expira. Añadir stale-while-revalidate elimina incluso el pico de la revalidación, porque nadie espera a que se regenere. Antes de optimizar el render, pregúntate si esa respuesta puede cachearse: convertir mil renders por minuto en uno cada minuto es la mayor ganancia de TTFB que existe.

Streaming de HTML

Astro no espera a tener el HTML completo para empezar a enviarlo: lo transmite por streaming a medida que lo genera. El navegador recibe la cabecera y el esqueleto de la página, y empieza a descargar estilos y a pintar, mientras el servidor sigue produciendo el resto. Esto hace que dónde colocas tus esperas importe enormemente.

El bloque de una página .astro corre entero antes de emitir su HTML. Si pones ahí una espera lenta que bloquea toda la página, retrasas el primer byte de todo. La alternativa es no bloquear el esqueleto con lo lento: dejar que el shell fluya y traer los datos tardíos por otra vía.

---
// MAL: esta espera lenta retrasa el primer byte de toda la pagina
const todo = await consultaLentisima();
---
<article>{todo.contenido}</article>
⚠️
Un await en el camino crítico bloquea toda la página

El error clásico del SSR es amontonar en la cabeza de la página todas las esperas, incluidas las que solo alimentan una esquina secundaria. Mientras esa promesa no resuelve, el servidor no emite ni una etiqueta, y el visitante mira una pantalla en blanco. La regla es simple: en el camino crítico, solo lo que el esqueleto necesita para existir. Todo lo demás —lo lento, lo secundario, lo que puede llegar un segundo después— debe salir de esa ruta y diferirse, para que el HTML empiece a fluir cuanto antes.

Server islands y el edge

La herramienta que Astro da para sacar lo lento del camino crítico es la server island. Ya la viste: server:defer renderiza al instante un contenido de reserva y produce la parte dinámica en una petición aparte que se inserta después, sin bloquear el esqueleto y sin un gramo de JavaScript de cliente. Es la forma idiomática de mezclar un shell cacheable con una pieza fresca por visita.

---
import Recomendados from '../components/Recomendados.astro';
---
<article>Contenido estático y cacheable de la página.</article>

<Recomendados server:defer>
  <p slot="fallback">Cargando recomendaciones...</p>
</Recomendados>

La última palanca es espacial: acercar el cómputo. Desplegar el SSR en el edge —servidores repartidos por el mundo, cerca del visitante— recorta la latencia de red y con ella el TTFB, porque la petición viaja menos. El precio es un entorno más limitado y la distancia a tus datos: si tu base de datos está lejos del edge, ganas en un lado lo que pierdes en el otro. Mídelo antes de decidir.

Prerenderizar lo que no cambia

La optimización más contundente de una ruta SSR es dejar de renderizarla por petición. En el modo híbrido de Astro, cada página decide su naturaleza: las que no dependen de la petición pueden prerenderizarse a HTML estático en el build y servirse con TTFB de archivo, reservando el SSR para lo que de verdad cambia por visita.

---
// Esta pagina se genera en el build, no por peticion
export const prerender = true;
---

El criterio es preguntar, ruta a ruta, si su contenido depende de quién pide o de cuándo pide. La portada de un blog, una ficha de producto que cambia cada hora, la documentación: casi todas pueden ser estáticas o cacheadas con revalidación, y solo el panel personalizado o el carrito necesitan renderizado fresco. Afinar SSR empieza por reducir cuánto SSR hay: la ruta más rápida es la que ya estaba escrita antes de que llegara la petición.

⏱️

TTFB primero

En SSR el reloj arranca en el servidor. Mídelo: todo lo demás espera detrás del primer byte.

🗄️

Caché en dos niveles

Cachea los datos con un TTL y la respuesta con s-maxage. La petición que no ejecutas es la más rápida.

🌊

Streaming

Deja fluir el shell y saca del camino crítico las esperas lentas para adelantar el primer byte.

🌍

Edge y server islands

Acerca el cómputo al visitante y difiere lo dinámico con server:defer, sin JavaScript de cliente.

flowchart LR
REQ[peticion entra] --> EDGE[servidor en el edge cercano]
EDGE --> C{respuesta en cache}
C -->|si| HIT[TTFB minimo sin render]
C -->|no| REN[renderiza y guarda en cache]
REN --> SHELL[envia el shell por streaming]
SHELL --> DEF[server islands llegan despues]
style HIT fill:#a6e3a1,color:#11111b
style SHELL fill:#89b4fa,color:#11111b
style DEF fill:#fab387,color:#11111b
Afinar SSR es programar la latencia en el tiempo y en el espacio

Toda la optimización del renderizado en servidor cabe en una idea que, una vez vista, ordena el resto: la latencia no es un coste fijo que se paga, sino un trabajo que se puede reprogramar —adelantándolo, aplazándolo o desplazándolo—. Míralo así y cada técnica de este capítulo deja de ser un truco aislado y se revela como un movimiento en una de dos dimensiones. En el eje del tiempo, la caché mueve el trabajo hacia atrás: lo haces una vez y lo reutilizas, de modo que la mayoría de las peticiones cobran el resultado de un cómputo que ya ocurrió. El streaming y las server islands lo mueven hacia adelante: emites lo que ya tienes sin esperar a lo que falta, y lo lento llega cuando esté, fuera del camino crítico. En el eje del espacio, el edge mueve el trabajo hacia el lado, acercándolo al visitante para que la distancia física deje de sumar milisegundos. Y por debajo de todas late el mismo principio que gobernaba el presupuesto de JavaScript y la coreografía de assets: el rendimiento no es hacer las cosas más rápido, es hacerlas en el momento y el lugar correctos, o no hacerlas en absoluto. El TTFB es solo el reloj que te dice si lo estás logrando. Un desarrollador que interioriza esto deja de ver el servidor como una caja que responde tan rápido como puede y empieza a verlo como un planificador: decide qué se calcula ahora y qué se sirve de antes, qué bloquea el primer byte y qué fluye después, qué corre junto a los datos y qué junto al usuario. Optimizar SSR, al final, no es acelerar el renderizado; es orquestar cuándo y dónde sucede cada parte, hasta que el visitante reciba lo antes posible lo primero que importa y el resto lo alcance sin que apenas lo note.

⚔️ Afina tu renderizado en servidor
  1. Mide el TTFB de una ruta SSR con curl y localiza en el bloque de la página la espera que más lo infla.
  2. Envuelve una consulta cara en una caché con tiempo de vida y comprueba cómo cae el TTFB en la segunda petición.
  3. Añade Cache-Control con s-maxage y stale-while-revalidate a una página que cambia poco y verifica que el CDN la sirve cacheada.
  4. Saca del camino crítico la parte más lenta convirtiéndola en una server island con server:defer y observa cómo el esqueleto llega antes.