¿Cómo forzar al navegador a recargar los archivos CSS/JS en caché?

He notado que algunos navegadores (en particular, Firefox y Opera) son muy celosos en el uso de copias en caché de los archivos .css y .js, incluso entre sesiones del navegador. Esto conduce a un problema cuando se actualiza uno de estos archivos pero el navegador del usuario sigue utilizando la copia en caché.

La pregunta es: ¿cuál es la forma más elegante de obligar al navegador del usuario a recargar el archivo cuando ha cambiado?

Lo ideal sería que la solución no obligara al navegador a recargar el archivo en cada visita a la página. Publicaré mi propia solución como respuesta, pero tengo curiosidad por saber si alguien tiene una solución mejor y dejaré que vuestros votos decidan.

Actualización :

Después de permitir la discusión aquí por un tiempo, he encontrado John Millikin y da5id's sugerencia para ser útil. Resulta que hay un término para esto: autoversión.

He publicado una nueva respuesta a continuación que es una combinación de mi solución original y la sugerencia de John'.

Otra idea que fue sugerida por SCdF sería añadir una cadena de consulta falsa al archivo. (Un código Python para usar automáticamente la marca de tiempo como una cadena de consulta falsa fue enviado por pi). Sin embargo, hay una discusión sobre si el navegador podría almacenar en caché un archivo con una cadena de consulta. (Recuerda que queremos que el navegador guarde el archivo en la caché y lo utilice en futuras visitas. Sólo queremos que recupere el archivo cuando haya cambiado).

Dado que no está claro qué ocurre con una cadena de consulta falsa, no acepto esa respuesta.

Actualización: Reescrito para incorporar las sugerencias de John Millikin y da5id. Esta solución está escrita en PHP, pero debería adaptarse fácilmente a otros lenguajes.

Actualización 2: Incorporación de los comentarios de Nick Johnson de que la regex original de .htaccess puede causar problemas con archivos como json-1.3.js. La solución es reescribir sólo si hay exactamente 10 dígitos al final. (Porque 10 dígitos cubren todas las marcas de tiempo desde el 9/9/2001 hasta el 20/11/2286).

Primero, usamos la siguiente regla de reescritura en el .htaccess:

RewriteEngine on
RewriteRule ^(.*)\.[\d]{10}\.(css|js)$ $1.$2 [L]

Ahora, escribimos la siguiente función PHP:

/**
 *  Given a file, i.e. /css/base.css, replaces it with a string containing the
 *  file's mtime, i.e. /css/base.1221534296.css.
 *  
 *  @param $file  The file to be loaded.  Must be an absolute path (i.e.
 *                starting with slash).
 */
function auto_version($file)
{
  if(strpos($file, '/') !== 0 || !file_exists($_SERVER['DOCUMENT_ROOT'] . $file))
    return $file;

  $mtime = filemtime($_SERVER['DOCUMENT_ROOT'] . $file);
  return preg_replace('{\\.([^./]+)$}', ".$mtime.\$1", $file);
}

Ahora, donde sea que incluyas tu CSS, cámbialo por esto:

A esto:

De esta manera, no tendrás que volver a modificar la etiqueta de enlace, y el usuario siempre verá el último CSS. El navegador podrá almacenar en caché el archivo CSS, pero cuando haga algún cambio en su CSS el navegador lo verá como una nueva URL, por lo que no utilizará la copia en caché.

Esto también puede funcionar con imágenes, favicons y JavaScript. Básicamente cualquier cosa que no sea generada dinámicamente.

Comentarios (34)

Puedes poner ?foo=1234 al final de tu importación css / js, cambiando 1234 por lo que quieras. Echa un vistazo a la fuente de SO html para un ejemplo.

La idea es que los parámetros de la letra "foo" son descartados o ignorados en la solicitud de todos modos y se puede cambiar ese número cuando se lanza una nueva versión.


Nota: Hay cierta discusión con respecto a cómo afecta esto exactamente al almacenamiento en caché. Creo que la idea general es que las peticiones GET, con o sin parámetros, deberían ser almacenadas en caché, por lo que la solución anterior debería funcionar.

Sin embargo, depende tanto del servidor web para decidir si quiere adherirse a esa parte de la especificación y el navegador que el usuario utiliza, ya que puede seguir adelante y pedir una versión fresca de todos modos.

Comentarios (8)

He oído que esto se llama "auto versionado". El método más común es incluir el mtime del archivo estático en algún lugar de la URL, y quitarlo usando manejadores de reescritura o confs de URL:

Ver también:

Comentarios (1)