Configurar headers HTTP Cache-Control para APIs y assets
Establece headers Cache-Control, ETag y Last-Modified para controlar el caching de navegadores y CDN para respuestas API y assets estaticos
Los headers de caching HTTP le dicen a navegadores y CDN cuanto tiempo cachear una respuesta, cuando revalidar y si la respuesta puede servirse desde una cache compartida. Headers configurados correctamente reducen latencia, bajan la carga del origen y mejoran Core Web Vitals. La solucion a continuacion cubre Cache-Control, ETag, Last-Modified y stale-while-revalidate tanto para respuestas API como para assets estaticos.
Cuando Usar Esto
-
For alternatives, see Complete Guide to GraphQL Caching.
-
Servir assets estaticos (JS, CSS, imagenes, fuentes) que cambian infrecuentemente
-
Respuestas API que son iguales para todos los usuarios o cambian en intervalos predecibles
-
Cualquier respuesta que se beneficia del caching en el edge del CDN Ver también Caché en Node.js con Redis: Cache-Aside, TTL e Invalidation.
Requisitos Previos
- Un servidor web o framework que permita establecer headers de respuesta
- Conocimiento basico del ciclo request/response HTTP
Solucion
1. Assets estaticos — cache larga con immutable
Assets estaticos con hashes de contenido en el nombre de archivo pueden cachearse agresivamente:
# nginx.conf
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff2?)$ {
expires 1y;
add_header Cache-Control "public, max-age=31536000, immutable";
}
// Express.js
app.use(express.static("public", {
maxAge: "1y",
setHeaders: (res, path) => {
if (path.endsWith(".js") || path.endsWith(".css")) {
res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
}
},
}));
El flag immutable le dice al navegador que nunca revalide — el nombre del archivo cambia cuando el contenido cambia (ej. app.abc123.js).
2. Respuestas API — cache corta con revalidacion
// Express.js — cachear respuestas API por 60 segundos con revalidacion
app.get("/api/products", async (req, res) => {
const products = await getProducts();
res.setHeader("Cache-Control", "public, max-age=60, stale-while-revalidate=300");
res.json(products);
});
// Sin caching para datos especificos de usuario
app.get("/api/users/me", authMiddleware, async (req, res) => {
const user = await getUser(req.userId);
res.setHeader("Cache-Control", "private, no-cache");
res.json(user);
});
3. ETag para peticiones condicionales
import crypto from "crypto";
app.get("/api/products", async (req, res) => {
const products = await getProducts();
const etag = `"${crypto.createHash("sha256").update(JSON.stringify(products)).digest("hex").slice(0, 16)}"`;
if (req.headers["if-none-match"] === etag) {
return res.status(304).end();
}
res.setHeader("ETag", etag);
res.setHeader("Cache-Control", "public, max-age=60");
res.json(products);
});
El cliente envia If-None-Match: "<etag>" en peticiones subsecuentes. Si el ETag coincide, el servidor retorna 304 Not Modified sin body — el cliente usa su copia cacheada.
4. Last-Modified para peticiones condicionales
app.get("/api/articles/:id", async (req, res) => {
const article = await getArticle(req.params.id);
const lastModified = new Date(article.updatedAt).toUTCString();
if (req.headers["if-modified-since"] === lastModified) {
return res.status(304).end();
}
res.setHeader("Last-Modified", lastModified);
res.setHeader("Cache-Control", "public, max-age=300");
res.json(article);
});
5. stale-while-revalidate para refresco en background
app.get("/api/trending", async (req, res) => {
const data = await getTrending();
// Cachear por 60s, luego servir obsoleto por hasta 300s mientras revalida
res.setHeader(
"Cache-Control",
"public, max-age=60, stale-while-revalidate=300"
);
res.json(data);
});
El CDN sirve la respuesta cacheada por 60 segundos. Entre 60-360 segundos, sirve la respuesta obsoleta mientras obtiene una copia fresca en background.
6. Ejemplo con Python / FastAPI
from fastapi import FastAPI, Request, Response
from fastapi.staticfiles import StaticFiles
import hashlib
import json
app = FastAPI()
app.mount("/static", StaticFiles(directory="public", max_age=31536000), name="static")
@app.get("/api/products")
async def get_products(request: Request):
products = await fetch_products()
body = json.dumps(products, default=str)
etag = f'"{hashlib.sha256(body.encode()).hexdigest()[:16]}"'
if request.headers.get("if-none-match") == etag:
return Response(status_code=304)
return Response(
content=body,
media_type="application/json",
headers={
"Cache-Control": "public, max-age=60, stale-while-revalidate=300",
"ETag": etag,
},
)
Como Funciona
max-age— el numero de segundos que la respuesta se considera fresca. El navegador sirve desde cache sin revalidacion durante este periodo.public— permite a caches compartidos (CDN, proxies) almacenar la respuesta. Usaprivatepara datos especificos de usuario.immutable— le dice al navegador que la respuesta nunca cambiara durante su vida util de frescura, omitiendo revalidacion condicional.ETag— una huella de contenido. El cliente enviaIf-None-Matchen peticiones subsecuentes; una coincidencia retorna304 Not Modified.stale-while-revalidate— despues de quemax-ageexpira, el CDN sirve contenido obsoleto mientras obtiene una copia fresca asincronamente, eliminando latencia para el usuario.
Variantes
No-Store para datos sensibles
app.get("/api/user/billing", authMiddleware, async (req, res) => {
res.setHeader("Cache-Control", "no-store");
res.json(billingData);
});
no-store previene que cualquier cache — navegador, CDN o proxy — almacene la respuesta.
Header Vary para negociacion de contenido
app.get("/api/products", (req, res) => {
res.setHeader("Vary", "Accept-Encoding, Accept-Language");
res.setHeader("Cache-Control", "public, max-age=300");
// La respuesta varia por encoding (gzip, br) y lenguaje
res.json(products);
});
Surrogate-Control para caching especifico de CDN
res.setHeader("Surrogate-Control", "max-age=3600");
res.setHeader("Cache-Control", "max-age=60");
Los CDN usan el TTL mas largo de Surrogate-Control, mientras los navegadores usan el TTL mas corto de Cache-Control.
Mejores Practicas
- Hashea nombres de archivo para assets estaticos — habilita caching
immutableconmax-age=31536000 - Usa
no-storepara datos sensibles — billing, tokens de auth, informacion personal - Establece
Varycorrectamente — omitirAccept-Encodingcausa que respuestas comprimidas y no comprimidas colisionen en cache - Usa
stale-while-revalidatepara APIs — elimina latencia visible para el usuario durante revalidacion
Errores Comunes
- Cachear respuestas especificas de usuario con
public— filtra datos entre usuarios a traves del CDN - Establecer
max-age=0sinno-cache—max-age=0fuerza revalidacion pero aun almacena la respuesta;no-storepreviene almacenamiento - Olvidar
Vary: Accept-Encoding— una respuesta gzipped cacheada para un cliente que no soporta gzip causa errores - Usar
Expiresen lugar deCache-Control—Expireses HTTP/1.0 y menos flexible; prefiereCache-Control
Troubleshooting
- Cache and database are out of sync: define a TTL or invalidation policy.
- Hit rate dropped after a deployment: check cache key generation and serialization changes.
- Cold cache causes thundering herd: use cache warming, request coalescing, or single-flight patterns for hot keys.
- Memory usage grows uncontrollably: set max memory policies, eviction thresholds, and key expiration. Audit large values.
- Stale data served to users: implement cache invalidation on write and cache-bust URLs for static assets.
Referencia Rápida
- Comando principal: ejecuta la solución base del artículo y verifica el resultado esperado.
- Validación: confirma que los tests pasan y que las métricas clave no se degradaron.
- Rollback: si algo falla, revierte el cambio y consulta la sección de Troubleshooting.
Lectura Adicional
- Documentación oficial: consulta la referencia actualizada del framework o herramienta utilizada.
- Guías relacionadas: explora las guías de http y caching para profundizar.
- Patrones complementarios: revisa los patrones de diseño aplicables a tu stack tecnológico.
- Postmortems públicos: estudia incidentes reales de equipos que enfrentaron problemas similares en producción.
Notas de Producción
- Despliega gradualmente usando canary o blue-green para detectar regresiones temprano.
- Configura alertas para errores, latencia p99 y tasa de fallos antes de habilitar en producción.
- Documenta el rollback en el runbook; prueba el procedimiento en staging al menos una vez por trimestre.
- Revisa logs estructurados con correlation IDs para trazar requests end-to-end en incidentes.
Puntos Clave
- Aplica configurar headers http cache-control para apis y assets cuando necesites una solución práctica para tu caso de uso.
- Monitorea el rendimiento después de implementar; mide latencia, errores y uso de recursos antes y después.
- Revisa la sección de Troubleshooting ante errores comunes; la mayoría tienen causa raíz documentada con solución.
- Mantén dependencias actualizadas y ejecuta tests en CI para prevenir regresiones en producción.
Errores Comunes en Producción
- Copiar el ejemplo sin adaptarlo a volúmenes y modos de fallo reales.
- Saltar tests de carga e inyección de errores antes del primer despliegue productivo.
- Codificar valores fijos que deberían ser configurables por entorno.
- Olvidar agregar logging y monitoreo en cada paso.
- Desplegar sin plan de rollback ni estrategia de backup probada.
- Asumir que el ejemplo mínimo escalará sin agregar caché o procesamiento por lotes.
- No documentar la versión y configuración usadas en producción.
- Dejar la receta sin cambios cuando evolucionan las dependencias o la escala.
Preguntas frecuentes
Cual es la diferencia entre no-cache y no-store?
no-cache almacena la respuesta pero requiere revalidacion antes de usarla. no-store previene el almacenamiento completamente. Usa no-store para datos sensibles.
Debo usar ETag o Last-Modified?
ETag es mas preciso (hash de contenido vs. timestamp). Usa ambos — los clientes que soportan ETag lo usan; los demas fallan a Last-Modified.
Por cuanto tiempo debo cachear assets estaticos?
Un ano (max-age=31536000) con immutable si los nombres de archivo tienen hash de contenido. De lo contrario, usa un TTL mas corto con revalidacion.
stale-while-revalidate funciona en navegadores?
Funciona en Chrome y Firefox. Safari lo ignora. CDN como Cloudflare y Fastly lo soportan independientemente del navegador.
Recursos Relacionados
Implementar el patron Cache-Aside con Redis
Usa el patron cache-aside para leer y escribir datos a traves de Redis, manejando cache misses, lecturas obsoletas e invalidacion write-through
PatternPatrón Cache-Aside
Carga datos en el caché bajo demanda desde el almacenamiento principal. Un patrón de caché que da a la aplicación control total sobre qué y cuándo cachear.
RecipeEstrategias y patrones de invalidacion de cache CDN
Implementa invalidacion de cache CDN usando purge APIs, surrogate keys, invalidacion por tags y URLs versionadas para mantener contenido fresco