HTTP caching sin magia: cómo mantener respuestas rápidas sin servir datos viejos

HTTP_caching

Una respuesta rápida no siempre es una respuesta actualizada. La caché solo puede ser útil si el sistema tiene una explicación verificable para conservar un dato, detectar que cambió y decidir qué hacer cuando la fuente no responde.

La política correcta depende del recurso y del costo de equivocarse. Un JavaScript versionado puede conservarse durante mucho tiempo; una respuesta personalizada debe restringirse a un caché privado; una API pública puede necesitar una vida corta y un validador. En equipos remotos, documentar ese contrato también evita que una persona en México, otra en otro estado o un cliente revisando el mismo despliegue interpreten “actualizar” de formas distintas.

Empieza por el riesgo, no por el número

RecursoDecisión inicialRiesgo a revisar
CSS, JavaScript o imagen con hash en la URLVida larga y immutableEl build debe generar otra URL cuando cambia el archivo
HTML o API pública cambianteVida corta y revalidaciónServir contenido desactualizado
Respuesta personalizadaprivate y política cautelosaExponer datos de una persona a un caché compartido
Respuesta que no debe almacenarseno-storePersistir información sensible o efímera

El estándar RFC 9111 de HTTP Caching describe el objetivo del caching como reutilizar respuestas para reducir tiempo de respuesta y ancho de banda sin cambiar la semántica de la transferencia. Eso no autoriza a elegir un max-age universal: el valor debe relacionarse con la frecuencia de cambio y con el daño que produciría una copia vieja.

Freshness: cuándo una copia todavía sirve

La cabecera Cache-Control contiene directivas para controlar cachés del navegador y cachés compartidos como proxies y CDNs, según la referencia de MDN sobre Cache-Control. Con max-age=600, una respuesta puede considerarse fresca durante 600 segundos desde su generación en el origin. No significa que cada usuario reciba necesariamente el mismo resultado: el tipo de caché, la petición y otras directivas también importan.

Cache-Control: public, max-age=120

Dos minutos pueden ser razonables para un catálogo público que cambia con frecuencia; pueden ser insuficientes para un asset inmutable o excesivos para un panel personalizado. En una conexión móvil o una ruta con latencia alta, un hit de caché puede reducir viajes al servidor, pero solo si la respuesta es segura para reutilizarse y el contrato se mantiene coherente.

No-cache no significa no almacenar

no-cache permite almacenar la respuesta, pero exige validarla antes de reutilizarla. no-store expresa la intención de no almacenarla. MDN distingue explícitamente ambas directivas en la documentación de Cache-Control. Confundirlas produce dos errores opuestos: una aplicación puede retener información que no debía persistir o puede eliminar una oportunidad segura de revalidación.

Cache-Control: no-cache

Cache-Control: no-store

Para una respuesta después de autenticación o con contenido personalizado, private indica que puede guardarse en un caché privado, como el navegador de esa persona, pero no en uno compartido. Esa frontera importa si el recurso viaja por un CDN usado por varios clientes. Si no sabes qué datos componen la representación, no empieces con public.

Revalidación: comprueba antes de transferir de nuevo

Un ETag identifica una versión de la representación. La referencia de MDN sobre ETag explica que el cliente puede enviar If-None-Match; si la etiqueta sigue coincidiendo, el servidor responde 304 Not Modified sin reenviar el cuerpo completo.

ETag: "catalogo-2026-09-02"

GET /api/productos
If-None-Match: "catalogo-2026-09-02"

HTTP/1.1 304 Not Modified

La etiqueta no es un botón de invalidación. Si la representación cambia, el origin debe producir otro valor. Un hash del cuerpo, una revisión de datos o una marca de modificación pueden ser estrategias válidas; lo importante es que dos representaciones diferentes no queden con la misma etiqueta por accidente.

Vary: una URL puede tener variantes

La misma URL puede producir representaciones distintas según Accept-Language, Accept-Encoding u otro header relevante. La guía de caching de MDN muestra cómo Vary informa qué entrada debe participar en la selección de una respuesta almacenada.

Vary: Accept-Language, Accept-Encoding

Prueba una política de caché con una respuesta fresca, una respuesta vencida y una fuente temporalmente indisponible. En cada caso registra qué se sirve, qué se vuelve a validar y qué mensaje recibe la persona usuaria.

Assets versionados: la estrategia más sencilla

Si el proceso de build publica app.3a9f1.js y luego app.8b21c.js, cada URL representa un archivo distinto. En ese caso, una política larga puede ser adecuada:

Cache-Control: public, max-age=31536000, immutable

La condición es estricta: nunca sobrescribas el archivo versionado con otro contenido y asegúrate de que el HTML nuevo apunte al nombre nuevo. Si una persona en un equipo remoto recibe HTML antiguo y el asset fue reemplazado en la misma URL, el problema no se corrige aumentando el tiempo de caché; se corrige con versionado confiable y un despliegue coherente.

API con ETag: un ejemplo comprobable

const express = require('express');
const crypto = require('crypto');
const app = express();

let productos = [
  { id: 1, nombre: 'Teclado', precio: 29.90 },
  { id: 2, nombre: 'Mouse', precio: 19.50 }
];

function etagPara(body) {
  const hash = crypto.createHash('sha256').update(body).digest('hex');
  return `W/"${hash}"`;
}

app.get('/api/productos', (req, res) => {
  const body = JSON.stringify(productos);
  const etag = etagPara(body);
  res.set('Cache-Control', 'public, max-age=120, must-revalidate');
  res.set('ETag', etag);
  res.type('application/json');

  if (req.headers['if-none-match'] === etag) {
    return res.status(304).end();
  }
  res.send(body);
});

El ejemplo muestra el flujo, no una configuración universal de producción. Debes decidir cómo serializar de forma estable, cómo tratar autorización, errores, orden de campos y cambios en la fuente de datos. Prueba la primera respuesta y luego copia el valor exacto de ETag:

curl -i http://localhost:3000/api/productos
curl -i http://localhost:3000/api/productos \
  -H 'If-None-Match: pega-aqui-el-etag'

El diagnóstico debe separar servidor, navegador y CDN

SíntomaPrimera comprobaciónHipótesis prudente
Se ve información antiguaAge, ETag, Last-Modified y headers de respuestaValidador o freshness incorrectos
Dos usuarios reciben variantes distintasVary, private, cookies y autorizaciónRespuesta personalizada en un caché compartido
El deploy parece no llegarURL del asset, HTML servido y caché del CDNAsset no versionado o purga incompleta
La aplicación tarda igualWaterfall, hit/miss, tamaño y originEl cuello de botella puede estar fuera del caché

En trabajo remoto, guarda una captura de headers, URL, hora y entorno cuando registres el incidente. “En mi navegador funciona” no permite que otra persona replique la prueba. Un informe breve con request, response, Age, ETag, región aproximada y paso de reproducción convierte una discusión asíncrona en evidencia técnica.

Una política útil para proyectos pequeños

Para un portafolio, una tienda pequeña o una API de un cliente, empieza clasificando cada recurso. Marca si es público o personalizado, si puede tolerar datos antiguos, si su URL cambia al desplegar y quién puede observar la respuesta. Esa clasificación es más útil que copiar una configuración de un tutorial.

Si trabajas desde México para un equipo distribuido, el artículo puede ayudarte a documentar decisiones que cruzan horarios y proveedores de red. No necesitas afirmar que una CDN concreta tiene la misma latencia en todas las ciudades; mide el entorno real del proyecto. El caché es una política de comportamiento, no una promesa de rendimiento universal.

Para relacionar esta práctica con otras capas, revisa la guía interna sobre consumo de APIs con fetch() y el artículo sobre automatización y despliegues. Ambas conexiones ayudan a comprobar si el cliente y el proceso de entrega respetan el contrato que el servidor publica.

Checklist antes de poner caching en producción

  • Clasifica el recurso como público, personalizado, versionado o sensible.
  • Define el costo de entregar una copia antigua y el tiempo de freshness aceptable.
  • Elige max-age, s-maxage, private, no-cache o no-store según esa decisión.
  • Genera un ETag nuevo cuando cambie la representación.
  • Declara variantes con Vary solo cuando una entrada realmente cambie la respuesta.
  • Versiona URLs de assets y no sobrescribas archivos inmutables.
  • Prueba navegador, caché compartido, CDN y origin por separado.
  • Documenta request, response y pasos de reproducción para el equipo remoto.

Un caché bien diseñado no oculta la antigüedad de una respuesta: la hace una decisión explícita. Cuando sabes qué puede reutilizarse, cómo se revalida y qué obliga a publicar una versión nueva, la velocidad deja de competir con la confianza.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Scroll al inicio