Saltar al contenido

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.

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.

Shell
# Medir el TTFB de una URL sin descargar el cuerpo enterocurl -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.

TypeScript
// Cachear datos en memoria con un tiempo de vidaconst 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.

TypeScript
// El CDN sirve cacheado 60 s y revalida sin bloquear durante 10 mincontext.response.headers.set(  'Cache-Control',  'public, s-maxage=60, stale-while-revalidate=600',);

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.

ASTRO
---// MAL: esta espera lenta retrasa el primer byte de toda la paginaconst todo = await consultaLentisima();---<article>{todo.contenido}</article>

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.

ASTRO
---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.

ASTRO
---// Esta pagina se genera en el build, no por peticionexport 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.