Un sitio estático es rápido, barato y fácil de operar. Pero tiene un límite: lo que muestra es lo que existía cuando se compiló. Si parte del contenido viene de APIs externas (artículos, redes sociales, repositorios, precios, estados), la página queda congelada hasta el siguiente build.
La salida habitual es programar rebuilds periódicos: cada hora, cada 6 horas, cada día. Funciona, pero tiene dos costos: el dato sigue llegando tarde y se compila el sitio entero solo para traer información que cambió.
En un proyecto personal pasé de rebuilds cada 6 horas a uno diario, combinando tres mecanismos. Este artículo explica cuáles son, cuándo conviene cada uno y qué resultados obtuve.
Mecanismo 1: el snapshot en el build
Durante la compilación, el servidor pide los datos y los escribe en el HTML.
// Solo se ejecuta al compilar
const res = await fetch(API_URL);
const datos = await res.json();
- A favor: el HTML llega completo (SEO, sin JavaScript, sin saltos de layout) y el visitante no consume cuota de ninguna API.
- En contra: el dato solo se actualiza al recompilar.
- Buena práctica: guarda un cache en el repositorio y úsalo si la API falla, para que un servicio externo caído nunca rompa tu build.
Este mecanismo no se elimina. Es la base sobre la que se montan los otros dos.
Mecanismo 2: el navegador consulta la API directamente
Cuando la página carga, un script pide el dato más reciente. Si es distinto de lo que ya se muestra, reemplaza la tarjeta. Si no, no hace nada.
try {
const res = await fetch(API_URL, { signal: AbortSignal.timeout(8000) });
if (!res.ok) return; // si falla, queda el snapshot
actualizarSiCambio(await res.json());
} catch { /* queda el snapshot */ }
Es mejora progresiva: la página ya funciona con los datos del build, y el JavaScript solo la mejora cuando puede.
Antes de usarlo, comprueba tres cosas:
-
CORS. La API debe permitir llamadas desde el navegador:
curl -D - -o /dev/null -H "Origin: https://tu-dominio.com" URLy buscaaccess-control-allow-origin. - Límites por IP. El límite se aplica a cada visitante, no a tu sitio. Si varios comparten red, la cuota se agota rápido.
- Secretos. Nada que viva en el navegador es privado. Un token ahí queda público.
Úsalo para APIs públicas, con CORS y sin límites estrictos.
Mecanismo 3: una función serverless con cache en el borde
Cuando la API tiene límites, necesita un token o quieres filtrar antes de mostrar, pon una función en medio. El navegador le pide los datos a tu propio dominio, y la función los obtiene y los guarda unos minutos en el cache del borde.
export async function onRequestGet({ request, waitUntil }) {
const cache = caches.default;
const clave = new Request(new URL(request.url).origin + '/api/datos');
const guardada = await cache.match(clave);
if (guardada) return guardada; // sin llamar a la API
const res = new Response(JSON.stringify(await obtenerDatos()), {
headers: { 'cache-control': 'public, max-age=300, s-maxage=600' },
});
waitUntil(cache.put(clave, res.clone())); // guardar sin hacer esperar
return res;
}
El efecto clave: la API externa recibe unas pocas peticiones por hora, sin importar cuántos visitantes entren. Los límites los absorbe el cache, no tus usuarios, y los tokens viven como secretos del servidor.
En este caso hay dos capas de cache: el navegador (5 minutos) y el borde (10 minutos). Un dato nuevo puede tardar hasta unos 15 minutos en verse.
Cómo elegir
| Pregunta | Mecanismo |
|---|---|
| ¿Cambia poco o no importa que tarde un día? | 1. Build |
| ¿Es una API pública, con CORS y sin límites que molesten? | 2. Navegador |
| ¿Tiene límites por IP, requiere token o necesita filtrarse? | 3. Función con cache |
En la práctica se combinan: cada tipo de dato en su mecanismo, todos sobre la misma base estática.
Que el build y el navegador digan lo mismo
El error más fácil de cometer en este diseño es que el build y el navegador filtren distinto: la página muestra una cosa y luego salta a otra. Dos decisiones lo evitaron:
- Un solo módulo de normalización, sin dependencias de Node, que importan el build, la función y el script del navegador (limpieza de títulos, recortes, descarte de duplicados, formato de fechas).
-
Un
<template>inerte con el mismo componente que usa el build. Para las tarjetas nuevas, el script lo clona y rellena, en lugar de reescribir el HTML en JavaScript.
Resultados
Esto es lo que medí o verifiqué, sin extrapolar:
- Rebuilds programados: de 4 al día a 1. Son 120 compilaciones al mes que pasan a 30, una reducción del 75 % del tiempo de build por esa vía, dentro del cupo gratuito de la plataforma.
- Frescura: el dato más cambiante pasó de llegar horas tarde a verse al abrir la página, y los datos con función, en unos 15 minutos.
- Cache: en una prueba local, la primera llamada a la función tardó cerca de 950 ms (consulta a la API externa) y la segunda 14 ms, servida desde el cache. En producción conviene medirlo con tu propio tráfico.
- Robustez: con las APIs bloqueadas la página siguió mostrando el snapshot, sin tarjetas rotas ni errores de consola.
- Costo adicional de infraestructura: ninguno. La función y el cache usan la capa gratuita de la plataforma.
No medí el impacto en tiempos de carga en producción, así que no afirmo mejora ahí. El HTML inicial sigue siendo el mismo.
Lecciones
- Mostrar primero lo guardado y actualizar después da lo mejor de ambos mundos: SEO y velocidad, más datos recientes.
-
Un
catchque se traga el error esconde problemas. Un 403 que solo ocurría en el entorno serverless (la API rechazaba peticiones sinUser-Agent) me costó tiempo hasta que registré el error en los logs. - Antes de mover un dato al navegador, revisa su límite por IP. Es el motivo más común para dejarlo en el build o en una función.
- Refrescar en vivo no sirve si la fuente se actualiza menos seguido. Si el dato original se regenera una vez al día, pedirlo cada minuto no lo hace más fresco.
Limitaciones
- Solo cubre lectura. Crear o editar datos requiere base de datos, validación en el servidor y autenticación.
- Detectar elementos editados o borrados no es trivial: mi script solo agrega lo más nuevo que lo ya mostrado, y los cambios esperan al siguiente rebuild.
- El diseño es un híbrido, no un reemplazo del rebuild: el rebuild diario sigue cubriendo lo que quedó en el mecanismo 1.
Conclusión
"Estático" no tiene por qué significar "congelado". Con un snapshot en el build como base, el navegador para APIs sencillas y una función con cache para las delicadas, un sitio estático se mantiene rápido, indexable y razonablemente al día, con menos compilaciones y sin montar un backend completo.
Si hoy dependes de un rebuild frecuente para mostrar datos nuevos, revisa cuáles de esos datos realmente lo necesitan. Probablemente solo una parte.
Top comments (0)