wandres.dev
STATIC ASSETS · la paridad con Pages

Caché y cabeceras: immutable, hashes y Cache-Control

Cómo se cachean los assets en el edge y en el navegador: el hash de contenido que hace inmutable a un archivo, la estrategia de dos niveles que cachea para siempre los recursos con hash y revalida el HTML, el archivo `_headers` para fijar `Cache-Control` por ruta, y el uso de `ETag` y las respuestas 304 para no reenviar bytes que ya están.

⏱ 16 min

Servir un archivo desde la ciudad más cercana al usuario ya es rápido; no volver a servirlo cuando no ha cambiado es gratis e instantáneo. Ahí entra la caché, la capa que decide cuánto tiempo un asset puede reutilizarse sin volver a pedirlo, y que separa un sitio meramente global de uno verdaderamente veloz. Cloudflare cachea tus assets en el edge y en el navegador de forma automática, pero el control fino —qué se cachea para siempre, qué se revalida en cada visita— lo gobiernas tú con dos herramientas: los hashes de contenido en los nombres de archivo y la cabecera Cache-Control. Esta lección explica la estrategia de dos niveles que hace que un despliegue nuevo sea a la vez instantáneo para el que vuelve y siempre fresco.

🎯 Al terminar esta lección sabrás
  • Entender cómo Cloudflare cachea los assets en el edge y en el navegador con ETag y respuestas 304.
  • Ver por qué un nombre de archivo con hash de contenido puede declararse immutable sin riesgo.
  • Aplicar la estrategia de dos niveles: cachear para siempre lo que tiene hash y revalidar el HTML.
  • Fijar Cache-Control por ruta con un archivo _headers en el directorio de assets.

Cómo se cachea un asset

Cuando el edge sirve un asset, no lo trata como bytes anónimos: le calcula un identificador basado en su contenido y lo expone como cabecera ETag. En la siguiente visita, el navegador reenvía ese identificador en una cabecera condicional, y si el archivo no ha cambiado el edge responde un 304 Not Modified —una respuesta minúscula, sin cuerpo, que le dice al navegador “usa el que ya tienes”—. Así se evita reenviar bytes idénticos: la validación cuesta un ida y vuelta diminuto en lugar de la descarga completa.

Sobre esa base actúa Cache-Control, la cabecera que decide cuánto tiempo puede el navegador —y las cachés intermedias— reutilizar un asset sin siquiera preguntar. Un max-age alto significa “no vuelvas a pedirlo en este tiempo”; un no-cache significa “puedes guardarlo, pero valida siempre antes de usarlo”. La diferencia entre revalidar y no preguntar es la diferencia entre una petición de red y ninguna, y aplicarla bien a cada tipo de archivo es el arte de esta lección.

El hash de contenido: la clave de lo inmutable

La pregunta incómoda de toda caché agresiva es: si le digo al navegador que guarde un archivo un año, ¿cómo le entrego una versión nueva antes de que pase ese año? La respuesta elegante es no cambiar nunca un archivo, sino publicar uno con otro nombre. Los empaquetadores modernos incrustan un hash del contenido en el nombre del archivo —app.4f3a2b1c.js—, de modo que cualquier cambio en el código produce un nombre distinto. El archivo con un nombre dado es, por construcción, inmutable: ese nombre solo puede referirse a ese contenido, para siempre.

Eso habilita la directiva más potente de Cache-Control: immutable. Declarar un archivo con hash como public, max-age=31536000, immutable le promete al navegador que ese archivo jamás cambiará, y el navegador deja de revalidarlo por completo —ni siquiera envía la petición condicional 304—. Es la máxima velocidad posible: el recurso se sirve desde el disco local sin tocar la red.

flowchart TD
D[Nuevo despliegue] --> H[El empaquetador pone un hash en el nombre]
H --> A[app.4f3a2b.js es inmutable por construccion]
A --> C[Cache-Control immutable un ano sin revalidar]
D --> I[index.html conserva su nombre]
I --> R[Cache-Control no-cache revalida en cada visita]
R --> N[El HTML apunta al nuevo hash y el navegador lo baja]
style A fill:#a6e3a1,color:#11111b
style R fill:#f9e2af,color:#11111b

La estrategia de dos niveles

De aquí sale el patrón que usan todos los sitios rápidos, y que combina las dos ideas anteriores. Los recursos con hash en el nombre —JavaScript, CSS, tipografías— se cachean para siempre y como inmutables, porque un cambio nunca reescribe un nombre existente sino que crea uno nuevo. En cambio, los puntos de entrada sin hash —el index.html y demás HTML— se revalidan en cada visita con no-cache, porque son ellos los que contienen las referencias a los nombres con hash. El HTML es el índice mutable que apunta a un mar de recursos inmutables.

El mecanismo encaja como un reloj: cuando despliegas una versión nueva, el HTML —que siempre se revalida— llega fresco y trae dentro los nuevos nombres con hash; el navegador ve que esos nombres son distintos de los que tenía y baja solo los que cambiaron, reutilizando de su caché local todos los que no. El que vuelve a tu sitio obtiene la versión nueva al instante y descarga el mínimo indispensable. Fresco y rápido a la vez, sin elegir entre ambos.

Fijar Cache-Control con _headers

Para aplicar esa estrategia colocas un archivo llamado _headers en la raíz de tu directorio de assets. Cada bloque asocia un patrón de ruta con las cabeceras que Cloudflare añadirá a esas respuestas; es la misma sintaxis que heredó de Pages, otra pieza de la paridad de 2026:

/assets/*
  Cache-Control: public, max-age=31536000, immutable

/*.html
  Cache-Control: no-cache

/
  Cache-Control: no-cache

Ese _headers cachea para siempre todo lo que cuelga de /assets/ —donde tu empaquetador deja los archivos con hash— y obliga a revalidar cada HTML y la raíz. El archivo no se sirve como contenido; wrangler lo interpreta al desplegar y aplica las reglas a las respuestas correspondientes. Si necesitas cabeceras calculadas en tiempo real —una política que dependa del usuario— siempre puedes servir el asset desde el Worker con env.ASSETS.fetch y reescribir las cabeceras de la respuesta antes de devolverla.

⚠️
Cachear el HTML para siempre es la trampa clásica

El error más común y más difícil de diagnosticar es aplicar un max-age largo al index.html. Como el HTML es la puerta que apunta a los recursos con hash, cachearlo agresivamente congela a los usuarios en una versión vieja: despliegas una corrección, pero quien ya visitó el sitio sigue recibiendo el HTML antiguo, que apunta a los hashes antiguos, durante todo el max-age. La regla es inviolable: hashea y cachea para siempre los recursos referenciados, revalida siempre los documentos que los referencian. Nunca al revés.

La caché es un contrato sobre qué puede cambiar y qué no

Toda estrategia de caché es, en el fondo, una respuesta a una única pregunta filosófica: ¿qué cosas de mi sistema tienen identidad estable y cuáles son mutables? La genialidad del hash de contenido es que no gestiona la invalidación de la caché —el problema notoriamente más difícil de la informática— sino que lo disuelve. No invalidas nunca un archivo con hash porque no lo cambias nunca; cuando su contenido cambia, deja de ser el mismo archivo y pasa a tener otro nombre. El hash convierte el nombre en una función pura del contenido, y con ello transforma un recurso mutable y traicionero en uno inmutable y eterno sobre el que puedes hacer las promesas de caché más agresivas sin miedo. Lo que queda mutable —el HTML— se aísla y se marca como tal, revalidándose siempre, precisamente porque es el único punto donde la identidad debe poder cambiar para apuntar a los nuevos contenidos. Fíjate en la estructura que emerge: has partido tu sistema en una capa inmutable enorme y una capa mutable diminuta, y has empujado toda la incertidumbre a esa capa pequeña que revalidas barato. Ese patrón —contenido inmutable direccionado por hash, más un índice mutable minúsculo que apunta a él— no es exclusivo de la caché web: es la misma idea que gobierna Git, los sistemas de archivos direccionados por contenido y los almacenes de datos inmutables. Aprender a cachear assets no es memorizar directivas de Cache-Control; es aprender a dividir cualquier sistema en la parte que puede prometerse eterna y la parte que debe permanecer libre para cambiar, y a hacer que la segunda sea lo más pequeña posible. Cuando esa división está bien hecha, la velocidad y la frescura dejan de estar en conflicto: son las dos caras de la misma decisión de diseño.

⚔️ Diseña la caché de dos niveles de tu sitio
  1. Compila un proyecto que genere nombres con hash para su JavaScript y su CSS, y observa cómo cambian esos nombres al modificar el código fuente.
  2. Añade un _headers que declare los recursos con hash como public, max-age=31536000, immutable y el HTML como no-cache; despliega y verifica las cabeceras con las herramientas de red.
  3. Recarga una segunda vez y confirma que los recursos con hash se sirven desde la caché local sin petición, mientras el HTML devuelve un 304.
  4. Despliega un cambio pequeño y comprueba que el navegador baja solo el recurso cuyo hash cambió y reutiliza el resto.
  5. Provoca a propósito la trampa: cachea el index.html con un max-age largo, despliega una corrección y razona por qué un usuario que ya visitó el sitio no la ve hasta que expira.