Saltar al contenido

Datos: request-time frente a build-time

El momento de render se paga en tres monedas que no puedes maximizar a la vez: frescura de los datos, latencia de la primera respuesta y coste de operación. Pedir un dato en el build o en la petición reparte esas monedas de forma opuesta. Anatomía del TTFB, la caché como palanca y la economía de pagar una vez frente a pagar por visita.

Dos momentos para pedir un dato

El mismo await fetch produce consecuencias distintas según cuándo corra. En una página horneada, la petición de datos ocurre una vez, durante el build; su resultado se cocina dentro del HTML y ese HTML se sirve inmutable hasta la próxima publicación. El dato es tan viejo como tu último despliegue, pero servirlo no cuesta ni una llamada.

ASTRO
---export const prerender = true;   // el fetch corre UNA vez, en el buildconst productos = await fetch('https://api.tienda/productos').then((r) => r.json());---<ul>{productos.map((p) => <li>{p.nombre}</li>)}</ul>

En una página bajo demanda, ese mismo fetch corre en cada visita. El dato está siempre fresco —refleja el estado del origen en el instante de pedirlo— pero cada visitante paga la espera de esa llamada, y tu servidor la ejecuta una y otra vez.

ASTRO
---export const prerender = false;  // el fetch corre en CADA peticionconst productos = await fetch('https://api.tienda/productos').then((r) => r.json());---

El código es idéntico salvo por una línea. Lo que cambia por completo es el reparto de las tres monedas: el build compra latencia mínima y coste nulo a cambio de frescura; la petición compra frescura a cambio de latencia y coste. No hay un fetch mejor que el otro, solo dos momentos con precios distintos.

Y entre esos dos extremos hay un continuo, no un abismo. Estas son algunas paradas intermedias, ordenadas de más fresco a más barato:

  • Bajo demanda sin caché: cada visita recomputa. Frescura al segundo, coste por visita.
  • Bajo demanda con caché corta: se guarda el resultado unos segundos o minutos. Casi fresco, coste amortizado.
  • Rebuild programado: reconstruyes el sitio cada hora o cada día. Estático, pero con datos periódicamente renovados.
  • Estático puro: se hornea una vez y vive hasta el próximo despliegue. Máxima velocidad y baratura, mínima frescura.

Todas esas tácticas mueven el mismo dial —cada cuánto se paga el cálculo— sin saltar de golpe de “una vez para siempre” a “en cada visita”. Pensar en frescura como un espectro, y no como un interruptor, es lo que abre las estrategias intermedias.

Caché y TTFB: de dónde sale la latencia

El TTFB —el tiempo hasta el primer byte— tiene una anatomía distinta en cada modo, y entenderla explica por qué lo estático se siente instantáneo. Para una página horneada, el visitante habla con la CDN: el único coste es la distancia física hasta el nodo del edge más cercano, que ya tiene el fichero listo. No hay cómputo, no hay origen, no hay base de datos en el camino. Milisegundos.

Para una página bajo demanda sin caché, la cadena es más larga: la petición viaja de la CDN al origen, el origen ejecuta tu función, tu función quizá espera a una API o a una base de datos, y solo entonces vuelve el primer byte. Desglosado, el TTFB de una página dinámica suele repartirse en cuatro sumandos:

  • El salto de red de la CDN al origen que ejecuta la función.
  • El arranque de la función si estaba dormida —el temido arranque en frío—.
  • Tu cómputo: el render del componente y la lógica del frontmatter.
  • La espera a la fuente: la API o la base de datos que consultas.

Solo el tercero está enteramente en tus manos; los otros tres explican por qué a veces optimizar tu código apenas mueve el TTFB, y por qué una fuente lenta arruina la latencia por muy afinado que esté tu render.

TypeScript
// Un endpoint bajo demanda cacheado en el edgeexport const prerender = false;export const GET = async () => {  const datos = await consultarFuente();  return new Response(JSON.stringify(datos), {    headers: { 'Cache-Control': 'public, s-maxage=60, stale-while-revalidate=300' },  });};

Merece la pena distinguir las tres piezas de esa cabecera, porque cada una gobierna un actor distinto. max-age le habla al navegador del visitante; s-maxage le habla a la CDN compartida, y suele ser el que de verdad quieres afinar; stale-while-revalidate autoriza a servir la copia vencida un rato mientras se recalcula por detrás, de modo que ningún visitante espera al recómputo. Y hay un detalle crítico: la caché compartida solo es lícita si la respuesta es igual para todos. En cuanto personalizas por cookie o cabecera, ese s-maxage público se vuelve peligroso, porque repartiría a unos los datos de otros.

El coste: pagar una vez o pagar por visita

La tercera moneda es la que menos se mira y más factura. Lo estático concentra su coste en el build: minutos de cómputo que pagas una vez al publicar, y a partir de ahí, ancho de banda barato de una CDN. Su propiedad más valiosa es que el coste no crece con el tráfico: servir el mismo fichero a mil o a un millón de personas cuesta prácticamente lo mismo, y una avalancha de visitas no despierta ningún servidor.

Lo bajo demanda invierte esa economía. No hay coste de build que hablar, pero cada petición invoca una función de servidor que se cobra: por invocación, por milisegundo de cómputo, por la llamada a la fuente de datos. El coste escala con el tráfico, linealmente. Diez veces más visitas es diez veces más cómputo facturado. Para lo que de verdad necesita ser dinámico, ese gasto está justificado; para lo que podría haberse horneado, es dinero quemado en recalcular eternamente la misma respuesta.

Un ejemplo aterriza la asimetría. Imagina una página vista un millón de veces al mes. Horneada, su coste es un puñado de segundos de build más el ancho de banda de la CDN, iguales tanto si la ven mil personas como un millón. Bajo demanda sin caché, ese millón de vistas son un millón de invocaciones de función y un millón de consultas a la fuente, una factura que crece en línea recta con el éxito. Si el contenido era el mismo para todos, has pagado un millón de veces por calcular lo que podías haber calculado una sola vez, y el tráfico —que debería ser una buena noticia— se convierte en una cuenta creciente.

La lección económica se resume en dos frases:

  • Lo que es igual para todos: págalo una vez en el build; repetir el pago por visita es puro desperdicio.
  • Lo que cambia por visita o por usuario: no hay atajo, cada respuesta cuesta, y la caché solo ayuda si varias visitas comparten el mismo resultado.

Cada extremo tiene además su coste oculto, y conviene nombrarlos para no llevarse sorpresas:

  • Lo estático paga su precio en el tiempo de build: un sitio de cientos de miles de páginas puede tardar mucho en construirse, y refrescar un solo dato obliga a reconstruir. Aquí es donde el compilador en Rust y Rolldown de Astro 7 marcan la diferencia.
  • Lo dinámico paga en arranques en frío y en la latencia de la fuente: una función dormida tarda en despertar, y una base de datos lenta se traga tu TTFB por mucho que tu código sea rápido.