Buenas prácticas de configuración de cache para tu web

Puede que te hayas fijado en que, a veces, al entrar en una web por segunda vez, no se ve como debería: algunos estilos están rotos y todo tiene un aspecto extraño. Normalmente, la causa son unas políticas de control de cache mal definidas que impiden recibir los cambios del último despliegue. En este artículo te enseño la configuración de cache adecuada y las técnicas que te ayudarán a que todos los usuarios vean la versión actualizada de tu web después de cada despliegue.
Si solo quieres las buenas prácticas para empezar a aplicarlas ya, salta directamente al final del artículo.
¿Cómo funciona la cache por dentro?
En cada petición a una web o recurso, tu navegador intenta descargar la menor cantidad de datos posible leyendo la información guardada en la cache local. Esto solo es posible si le damos al navegador instrucciones suficientes sobre qué recursos debe guardar y durante cuánto tiempo.
Estas instrucciones funcionan como directivas; para comunicárselas al navegador, hay que añadirlas a las cabeceras HTTP de la respuesta. Las directivas más habituales en el proceso de cache son «Cache-Control», «Expires», «Etag» y «Last-Modified».
Casi todos los servidores web incluyen por defecto algún ajuste de cache en las cabeceras de respuesta, pero no está tan claro qué ocurre si no hay ninguna política de cache.
Sin configuración de control de cache, el navegador acude al servidor web en cada petición de recursos y lee la información desde allí. Esto aumenta el tiempo de carga de la web, añade carga extra al servidor al transferir la información e incrementa el número de llamadas a tu backend.
Sin instrucciones del servidor, es el navegador quien decide qué hacer y cómo cachear la información. Actualmente, Chrome y Safari descargan los datos del backend cada vez si no hay instrucciones de cache. Pero esto puede cambiar y dar lugar a comportamientos distintos, sobre todo en otras plataformas.
Para definir con claridad qué hacer con cada tipo de archivo, vamos a profundizar en las directivas de control de cache, añadiéndolas paso a paso a las cabeceras de respuesta y observando el resultado.
Etag (Entity tag)
Etag es uno de los ajustes de cache. La idea principal de esta cabecera HTTP es que el navegador sepa si un recurso ha cambiado sin tener que descargar el archivo completo. El servidor puede calcular algo parecido a un hash de cada archivo y enviárselo al cliente. La próxima vez que el cliente quiera acceder a ese recurso, en lugar de descargar el archivo, el navegador enviará en la cabecera HTTP algo así: If-None-Match: W/“1d2e7–1648e509289”. El servidor comparará ese hash con el del archivo actual y, si son distintos, obligará al cliente a descargar el archivo nuevo. Si no, le indicará que use la versión en cache.
Con una política de cache basada en Etag, siempre se consulta al servidor para comprobar el hash del archivo, y solo después el navegador decide si lo toma de la cache o lo descarga entero. Cuando un recurso no ha cambiado, la comprobación ocupa apenas 80–100 bytes, da igual lo que pidas: un archivo de 10 KB o de 10 MB.
Last Modified
Otro ajuste de control de cache es la cabecera HTTP «Last Modified». La idea es muy parecida a la de Etag, pero el comportamiento del navegador es algo distinto. Los servidores guardan la fecha de última modificación de cada archivo; tras la primera carga, el cliente puede preguntar al servidor si el recurso ha cambiado desde el momento concreto en que accedió a él por última vez. Para ello, el navegador envía If-Modified-Since: Fri, 13 Jul 2018 10:49:23 GMT en la cabecera HTTP. Si el recurso ha cambiado, el navegador tiene que descargar el archivo nuevo; si no, usa la versión en cache.
Lo cierto es que los navegadores tienen sus propias políticas internas de cache y pueden decidir por su cuenta si toman un recurso de la cache o descargan una copia nueva.
Last-Modified es una cabecera de cache débil, ya que el navegador aplica una heurística para decidir si toma el elemento de la cache o no, y esa heurística varía de un navegador a otro.
En consecuencia, no podemos fiarnos solo de Last-Modified, así que prefiero eliminarlo por completo de la configuración del servidor para reducir tráfico, aunque sean unos pocos bytes.
Cache-Control max-age
Esta directiva permite indicar al navegador durante cuánto tiempo debe conservar un archivo en la cache desde la primera carga. Ese tiempo se define en segundos y suele expresarse así: Cache-Control: max-age=31536000. Con esta política, el navegador se salta por completo las peticiones al servidor y abre los archivos muy rápido. Pero ¿cómo podemos estar seguros de que el archivo no cambiará en tanto tiempo? No podemos.
Por eso, para obligar al navegador a descargar la nueva versión de un archivo, usamos una técnica que implementan muchas herramientas de build de assets, como Webpack o Gulp. Cada archivo se precompila en el servidor y se añade un hash a su nombre, por ejemplo «app-72420c47cc.css». Hasta el cambio más pequeño en el archivo se refleja en el hash, lo que garantiza que se reconozca como un archivo distinto. Así, tras el siguiente despliegue, recibirás la nueva versión del archivo. Esto puede aplicarse a todos los archivos css, js e imágenes (max-age=31536000): cuando cambiamos algo, el navegador simplemente pide el archivo nuevo con su nuevo hash y lo guarda en cache.
no-cache
Lo delicado de la técnica anterior es que no puedes olvidarte de los archivos html: si les aplicas también esa configuración, nunca recibirás los enlaces nuevos a tus archivos css, js o imágenes hasta que fuerces una recarga.
Te recomiendo aplicar Cache-Control: no-cache a los archivos html. Usar «no-cache» no significa que no haya cache en absoluto; simplemente le indica al navegador que valide los recursos con el servidor antes de usarlos desde la cache. Por eso hay que combinarlo con Etag, de modo que el navegador envíe una petición sencilla y cargue esos 80 bytes extra para comprobar el estado del archivo.
Configuración final
- Usa Gulp, Webpack o una herramienta similar para añadir un hash único a tus archivos css, js e imágenes (como app-67ce7f3483.css).
- Para archivos js, css e imágenes, configura Cache-Control: public, max-age=31536000, sin Etag ni Last-Modified.
- Para archivos html, usa Cache-Control: no-cache y Etag.
Como ves, incluso algo tan evidente y habitual como cachear archivos estáticos deja de ser obvio cuando profundizas. Investigar bien te evitará errores, reducirá el tráfico de tu servidor y mejorará los tiempos de carga de tu web.