Objetivos de aprendizaje
Después de leer este artículo podrás:
- Entender qué hacen realmente max-age, no-cache, no-store e immutable
- Elegir el Cache-Control correcto para HTML, assets, imágenes y respuestas de API
- Configurar el encabezado en un servidor Node.js y verificar el resultado con curl
En esta página
Cada vez que abres un sitio, el navegador decide si descarga los archivos otra vez o reutiliza una copia guardada. Quien manda en esa decisión es el servidor, mediante el encabezado Cache-Control. Bien configurado, hace el sitio más rápido y reduce la carga del servidor. Mal configurado, hace que el visitante vea una versión vieja durante días.
Si la CDN todavía es una caja negra para ti, lee antes qué es una CDN.
¿Qué hace Cache-Control?#
Cache-Control es un encabezado de respuesta (y también de solicitud) de HTTP. Trae una lista de directivas separadas por coma que valen para todas las cachés del camino: la del navegador (privada, de un solo usuario) y las compartidas, como CDN y proxies inversos. Las reglas están en la RFC 9111.
HTTP/2 200
content-type: text/css
cache-control: public, max-age=31536000, immutableSin ninguna información de frescura, la caché puede estimar un plazo por su cuenta (caché heurística), normalmente una fracción del tiempo desde la última modificación. Por eso la regla es: declara lo que quieres, no dejes la decisión a la caché.
¿Qué directivas importan en el día a día?#
max-age=N: la respuesta se considera fresca durante N segundos, contados desde que el servidor la generó. Fresca significa que la caché la entrega sin preguntar nada al servidor.s-maxage=N: lo mismo, pero solo para cachés compartidas (CDN). Sobrescribe elmax-ageen ellas, y el navegador lo ignora.no-cache: puede guardarse, pero debe revalidarse con el servidor antes de cada reutilización. No significa "no uses caché".no-store: ninguna caché puede guardar la respuesta. Úsalo para datos sensibles.publicyprivate:privatelimita el almacenamiento a la caché del navegador del usuario;publicpermite cachés compartidas, incluso para solicitudes conAuthorization.immutable: el contenido no cambia mientras esté fresco, así que el navegador ni siquiera revalida al recargar. Solo tiene sentido cuando la URL cambia junto con el contenido.must-revalidate: una vez vencida, la respuesta nunca se sirve sin revalidar.max-age=0, must-revalidateequivale en la práctica ano-cache.stale-while-revalidate=N: una vez vencida, la caché puede entregar la copia vieja durante N segundos más mientras busca la nueva en segundo plano.stale-if-error=N: si el servidor responde con error (500, 502, 503 o 504), la caché puede entregar la copia vieja hasta por N segundos.
El error más común es creer que no-cache impide el almacenamiento. Quien lo impide es no-store.
¿Cómo funciona la revalidación?#
Cuando la copia vence, la caché no necesita descargar el archivo entero otra vez. Pregunta al servidor si algo cambió, enviando If-None-Match con el ETag que recibió (o If-Modified-Since con el Last-Modified). Si nada cambió, el servidor responde 304 Not Modified, sin cuerpo, y la copia vuelve a ser fresca. Si cambió, responde 200 con el archivo nuevo.
Ese es el mecanismo detrás de no-cache: el navegador siempre hace un viaje al servidor, pero casi siempre recibe solo un 304 de pocos bytes.
¿Qué Cache-Control usar para cada tipo de archivo?#
| Tipo de respuesta | Encabezado | Por qué |
|---|---|---|
JS, CSS e imágenes con hash en el nombre (app.3f9a1c.js) |
public, max-age=31536000, immutable |
La URL cambia cuando cambia el contenido, así que puede quedarse un año |
index.html y páginas que apuntan a los assets |
no-cache |
Siempre revalida y pasa a apuntar a los archivos nuevos |
| Imagen o archivo sin hash en el nombre | public, max-age=86400 (o menos) |
Acepta quedar desactualizado por un tiempo conocido |
| Respuesta de API personalizada por usuario | private, no-cache |
Nunca va a una caché compartida |
| Dato sensible (token, estado de cuenta) | no-store |
No puede quedar guardado en ningún lugar |
| Contenido público que puede atrasarse unos minutos | max-age=60, stale-while-revalidate=300 |
Respuesta instantánea y actualización en segundo plano |
| TTL distinto en la CDN y en el navegador | max-age=60, s-maxage=3600 |
La CDN guarda 1 hora, el navegador 1 minuto |
La lógica de las dos primeras filas es la más importante: el HTML nunca queda mucho tiempo en caché y los archivos versionados quedan un año. Cada deploy genera nombres nuevos, el HTML nuevo apunta a ellos y nadie ve una versión vieja. Herramientas como Vite ya ponen el hash en el nombre. Más en qué es un sitio estático.
¿Cómo configurarlo en Node.js (Express)?#
express.static acepta las opciones maxAge e immutable. Con setHeaders cambias el encabezado según el archivo. Ejemplo con Express 5:
import express from "express";
const app = express();
// Archivos con hash en el nombre: un año, inmutables.
app.use(
"/assets",
express.static("dist/assets", { maxAge: "1y", immutable: true }),
);
// El resto (index.html, etc.): siempre revalida.
app.use(
express.static("dist", {
setHeaders(res) {
res.setHeader("Cache-Control", "no-cache");
},
}),
);
// API con datos del usuario: nunca en caché compartida.
app.get("/api/me", (req, res) => {
res.set("Cache-Control", "private, no-cache");
res.json({ name: "Ana" });
});
app.listen(process.env.PORT ?? 3000);El primer app.use responde con Cache-Control: public, max-age=31536000, immutable. En otros servidores la idea es la misma: en Nginx, usa add_header Cache-Control "public, max-age=31536000, immutable"; dentro de location /assets/.
¿Cómo verificar que funcionó?#
Pide solo los encabezados con curl -I y revisa el cache-control:
curl -sI https://tu-dominio.com/assets/app.3f9a1c.js | grep -i -E "cache-control|^age|etag"Si hay una CDN en el camino, un encabezado age mayor que cero indica que la respuesta vino de su caché. En el navegador, la pestaña Red (Network) de las herramientas de desarrollo muestra "(disk cache)" o "(memory cache)" cuando se reutilizó la copia local. Ojo: recargar con F5 o Ctrl+Shift+R envía max-age=0 o no-cache en la solicitud, así que prueba abriendo la URL en una pestaña nueva.
¿Qué errores perjudican más?#
- Max-age largo en un archivo sin hash. El visitante se queda con el CSS viejo hasta que venza. Usa plazos largos solo con URL versionada.
- Max-age largo en el HTML. Aunque los assets sean nuevos, el navegador no busca el HTML que apunta a ellos.
- Usar
no-cacheesperando bloquear la caché. Para eso esno-store. - Cachear una respuesta personalizada como
public. Una CDN puede entregar los datos de un usuario a otro. Usaprivate. - Mezclar directivas en conflicto.
private, no-cache, no-store, max-age=0, must-revalidateequivale ano-store; elige una sola intención. - Olvidar la caché de la CDN después de corregir. Cambiar el encabezado no limpia lo que ya se guardó; hace falta un purge o esperar a que venza.
Cómo hacerlo en Square Cloud#
En Square Cloud, todos los planes incluyen una CDN global que respeta el Cache-Control que envía tu aplicación. La receta de esta guía funciona tal cual: hash en el nombre de los assets con max-age de un año, no-cache en el HTML y private o no-store en lo que pertenece a un usuario. Defines los encabezados en el código, como en el ejemplo con Express, haces el deploy y lo compruebas con curl -I en el subdominio *.squareweb.app que toda aplicación web recibe con HTTPS.
Cuando un archivo equivocado ya quedó en caché, usa la purga de caché, disponible en el subdominio squareweb.app y en un dominio personalizado (desde el plan Standard), desde el panel o la extensión de VS Code. Borra la caché de todo el host, así que úsala como corrección puntual y deja que las URLs versionadas se encarguen de los deploys del día a día.
Para los archivos que suben tus usuarios, Blob Storage permite definir el Cache-Control por objeto y reglas de caché por carpeta, sin pasar por tu aplicación. Consulta la purga en dominios personalizados y la CDN en red.
Red
Dominio propio, TLS automático y CDN global para tu app
Preguntas frecuentes
¿Cuál es la diferencia entre no-cache y no-store?
no-cache permite guardar la respuesta, pero obliga a revalidar con el servidor antes de reutilizarla. no-store prohíbe que cualquier caché la guarde. Para datos sensibles, usa no-store.
¿Cuál es el valor máximo de max-age?
No hay un límite práctico que debas memorizar: 31536000 segundos (un año) es el valor habitual para archivos versionados. Los valores negativos o decimales tienen comportamiento indefinido.
¿Qué es s-maxage?
Es un tiempo de frescura válido solo para cachés compartidas, como CDN y proxies. Sobrescribe el max-age en ellas, y el navegador lo ignora.
¿Por qué el navegador no toma la versión nueva de mi archivo?
Casi siempre el archivo se sirvió con un max-age largo y la URL no cambió. Renombra el archivo con un hash del contenido o reduce el max-age de lo que cambia a menudo, como el HTML.
Más en Redes y dominios
Ver los 27 artículos- Qué es un balanceador de carga y para qué sirveUn balanceador de carga reparte las solicitudes entre varios servidores. Aprende cómo funciona, los algoritmos, capa 4 y 7 y un ejemplo con nginx.
- Registros DNS: A, CNAME, MX y TXTLos registros DNS son las instrucciones de una zona que indican a dónde apunta un nombre. Entiende A, AAAA, CNAME, MX, TXT, NS y CAA con ejemplos y comandos dig.
- Cómo apuntar un dominio a tu hostingPara apuntar un dominio a tu hosting, crea un registro CNAME (o A) en el DNS de tu registrador. Pasos, cómo comprobarlo y los errores más comunes.
- Qué es un certificado SSL y para qué sirveSSL/TLS es el protocolo que cifra la conexión entre el navegador y el servidor y demuestra que el sitio es genuino. Mira cómo funciona y qué es el certificado.