Ir al contenido

Cache, ETag y límites

Las respuestas de contenido son cacheables y traen validador. Si respetas los headers, la mayoría de las visitas repetidas no transfieren cuerpo.

HTTP/1.1 200 OK
Content-Type: application/json
Cache-Control: public, max-age=0, s-maxage=60, stale-while-revalidate=60
ETag: W/"…"
DirectivaQué hace
max-age=0El navegador siempre revalida. Nunca sirve una copia local sin preguntar.
s-maxage=60Las cachés compartidas (CDN) pueden servir la respuesta hasta 60 segundos sin consultar al origen.
stale-while-revalidate=60Pasados esos 60 segundos, la CDN puede seguir sirviendo la copia vieja hasta 60 segundos más mientras refresca por detrás.

La consecuencia práctica: un cambio del comerciante puede tardar hasta unos dos minutos en verse en todos lados. No es un bug; es el margen de la caché de borde.

Del lado del origen hay una segunda caché, por tienda y por tipo de sección (content:{store}:{modulo}, con módulos como image-carousel o spec-carousel). Cada edición del comerciante —contenido, visibilidad, orden, archivado o estilo— invalida únicamente las entradas de su tienda, así que el contenido nuevo entra en la respuesta en cuanto la caché de borde revalida. No hay nada que purgar desde el tema.

El ETag es un validador opaco. Guárdalo y devuélvelo en la siguiente petición:

GET /api/public/content/{token}/product/333333174
If-None-Match: W/"…"

Si nada cambió, la API responde:

HTTP/1.1 304 Not Modified
Cache-Control: public, max-age=0, s-maxage=60, stale-while-revalidate=60
ETag: W/"…"

Sin cuerpo. Ahorras toda la transferencia del JSON.

Desde un tema, deja que el navegador se encargue. Su caché HTTP envía el If-None-Match por su cuenta y te entrega el cuerpo cacheado cuando llega un 304. Desde tu código eso se ve como un 200 normal, sin que tengas que guardar nada.

Es una razón para no desactivar la caché con cache: 'no-store': pierdes la revalidación gratuita.

Sin CORS de por medio, ahí sí administras el ETag tú mismo:

let etag = null;
let cacheado = null;
async function traerConEtag(url) {
const res = await fetch(url, {
headers: etag ? { 'If-None-Match': etag } : {},
});
if (res.status === 304) return cacheado;
etag = res.headers.get('ETag');
cacheado = await res.json();
return cacheado;
}

600 peticiones por minuto, por tienda. El límite es agregado: lo comparten todos los visitantes de esa tienda, no es por IP ni por sesión.

Al superarlo, la API responde 429.

En uso normal desde un tema no se alcanza: una visita hace una o dos llamadas, y la caché de borde absorbe el grueso del tráfico repetido. Si lo estás viendo, casi siempre es una llamada por sección en vez de una por página.

La caché HTTP resuelve las visitas repetidas. Lo que no resuelve son las llamadas duplicadas dentro de una misma carga: tres secciones del tema pidiendo el mismo bundle al mismo tiempo son tres requests, porque ninguna terminó todavía cuando empiezan las otras.

Eso se arregla compartiendo la promesa entre secciones, que es lo que hace traerBundle en el módulo cliente.