Question Comment forcer le navigateur à recharger les fichiers CSS / JS mis en cache?


J'ai remarqué que certains navigateurs (en particulier, Firefox et Opera) sont très zélés en utilisant des copies en cache de .css et .js fichiers, même entre les sessions du navigateur. Cela entraîne un problème lorsque vous mettez à jour l'un de ces fichiers, mais le navigateur de l'utilisateur continue d'utiliser la copie mise en cache.

La question est: quelle est la manière la plus élégante de forcer le navigateur de l'utilisateur à recharger le fichier quand il a changé?

Idéalement, la solution ne forcerait pas le navigateur à recharger le fichier à chaque visite de la page. Je vais poster ma propre solution comme réponse, mais je suis curieux de savoir si quelqu'un a une meilleure solution et je laisserai vos votes décider.

Mettre à jour:

Après avoir laissé la discussion ici pendant un moment, j'ai trouvé John Millikin et da5idla suggestion d'être utile. Il se trouve qu'il y a un terme pour cela: auto-versioning.

J'ai posté une nouvelle réponse ci-dessous qui est une combinaison de ma solution originale et de la suggestion de John.

Une autre idée qui a été suggérée par SCdF serait d'ajouter une chaîne de requête bidon au fichier. (Du code Python pour utiliser automatiquement l'horodatage comme une chaîne de requête bidon a été soumise par pi.). Cependant, il y a une certaine discussion quant à savoir si le navigateur mettrait en cache un fichier avec une chaîne de requête. (Rappelez-vous que nous voulons que le navigateur mette en cache le fichier et l'utilise lors de prochaines visites. Nous voulons seulement qu'il récupère le fichier quand il a changé.)

Puisque ce qui se passe avec une chaîne de requête bidon n'est pas clair, je n'accepte pas cette réponse.


865
2017-09-23 03:07


origine


Réponses:


Mettre à jour:  Réécrit pour intégrer les suggestions de John Millikin et da5id. Cette solution est écrite en PHP, mais devrait être facilement adaptée à d'autres langages.

Mise à jour 2: Intégrer les commentaires de Nick Johnson que l'original .htaccess regex peut causer des problèmes avec des fichiers comme json-1.3.js. La solution est de réécrire seulement s'il y a exactement 10 chiffres à la fin. (Parce que 10 chiffres couvre tous les horodateurs du 9/9/2001 au 20/11/2286.)

Tout d'abord, nous utilisons la règle de réécriture suivante dans .htaccess:

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

Maintenant, nous écrivons la fonction PHP suivante:

/**
 *  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);
}

Maintenant, où que vous incluiez votre CSS, changez-le:

<link rel="stylesheet" href="/css/base.css" type="text/css" />

Pour ça:

<link rel="stylesheet" href="<?php echo auto_version('/css/base.css'); ?>" type="text/css" />

De cette façon, vous n'avez plus jamais à modifier le tag de lien, et l'utilisateur verra toujours le dernier CSS. Le navigateur pourra mettre en cache le fichier CSS, mais lorsque vous apporterez des modifications à votre CSS, le navigateur le verra comme une nouvelle URL, il n'utilisera donc pas la copie mise en cache.

Cela peut également fonctionner avec des images, favicons et JavaScript. Fondamentalement tout ce qui n'est pas généré dynamiquement.


415
2017-09-23 04:04



Technique simple côté client

En général, la mise en cache est bonne. Il existe donc plusieurs techniques, selon que vous résolvez le problème vous-même lorsque vous développez un site Web ou que vous essayez de contrôler le cache dans un environnement de production.

Les visiteurs généraux de votre site Web n'auront pas la même expérience que celle que vous avez lorsque vous développez le site. Comme le visiteur moyen arrive moins fréquemment sur le site (peut-être seulement quelques fois par mois, sauf si vous êtes un réseau Google ou hi5), il est moins probable que vos fichiers soient en cache, ce qui peut suffire. Si vous souhaitez forcer une nouvelle version dans le navigateur, vous pouvez toujours ajouter une chaîne de requête à la requête et augmenter le numéro de version lorsque vous apportez des modifications majeures:

<script src="/myJavascript.js?version=4"></script>

Cela permettra à tout le monde d'obtenir le nouveau fichier. Cela fonctionne parce que le navigateur regarde l'URL du fichier pour déterminer s'il a une copie dans le cache. Si votre serveur n'est pas configuré pour faire quoi que ce soit avec la chaîne de requête, il sera ignoré, mais le nom ressemblera à un nouveau fichier dans le navigateur.

D'un autre côté, si vous développez un site Web, vous ne souhaitez pas modifier le numéro de version chaque fois que vous enregistrez une modification dans votre version de développement. Ce serait fastidieux.

Donc, pendant que vous développez votre site, une bonne astuce serait de générer automatiquement un paramètre de chaîne de requête:

<!-- Development version: -->
<script>document.write('<script src="/myJavascript.js?dev=' + Math.floor(Math.random() * 100) + '"\><\/script>');</script>

L'ajout d'une chaîne de requête à la requête est un bon moyen de mettre à jour une ressource, mais pour un simple site Web, cela peut être inutile. Et rappelez-vous, la mise en cache est une bonne chose.

Il est également intéressant de noter que le navigateur n'est pas nécessairement avare de conserver des fichiers dans le cache. Les navigateurs ont des politiques pour ce genre de chose, et ils jouent généralement selon les règles définies dans la spécification HTTP. Lorsqu'un navigateur fait une demande à un serveur, une partie de la réponse est un en-tête EXPIRES .. une date qui indique au navigateur combien de temps il doit être conservé dans le cache. La prochaine fois que le navigateur rencontre une requête pour le même fichier, il voit qu'il a une copie dans le cache et se tourne vers la date EXPIRES pour décider s'il doit être utilisé.

Alors croyez-le ou non, c'est en fait votre serveur qui rend le cache du navigateur si persistant. Vous pouvez ajuster les paramètres de votre serveur et changer les en-têtes d'EXPIRES, mais la petite technique que j'ai écrite ci-dessus est probablement un moyen beaucoup plus simple de vous y prendre. Étant donné que la mise en cache est bonne, vous souhaitez généralement définir cette date dans un avenir lointain (un "futur lointain expire en-tête") et utiliser la technique décrite ci-dessus pour forcer un changement.

Si vous êtes intéressé par plus d'informations sur HTTP ou comment ces demandes sont faites, un bon livre est "High Performance Web Sites" par Steve Souders. C'est une très bonne introduction au sujet.


162
2017-09-23 13:25



Google mod_pagespeed plugin pour Apache fera l'auto-versioning pour vous. C'est vraiment lisse.

Il analyse HTML à sa sortie du serveur web (fonctionne avec PHP, rails, python, HTML statique - quoi que ce soit) et réécrit les liens vers CSS, JS, les fichiers image afin qu'ils incluent un code d'identification. Il sert les fichiers aux URLs modifiées avec un très long contrôle de cache sur eux. Lorsque les fichiers changent, il modifie automatiquement les URL afin que le navigateur les récupère. Il fonctionne essentiellement, sans aucun changement à votre code. Cela va même minifier votre code à la sortie.


110
2017-09-23 03:12



Au lieu de changer la version manuellement, je vous recommande d'utiliser un hachage MD5 du fichier CSS réel.

Donc, votre URL serait quelque chose comme

http://mysite.com/css/[md5_hash_here]/style.css

Vous pouvez toujours utiliser la règle de réécriture pour supprimer le hachage, mais l'avantage est que vous pouvez désormais définir votre stratégie de cache sur "cache forever", car si l'URL est la même, cela signifie que le fichier est inchangé.

Vous pouvez ensuite écrire un script shell simple qui calcule le hachage du fichier et met à jour votre balise (vous voudrez probablement le déplacer dans un fichier séparé pour l'inclure).

Il suffit de lancer ce script chaque fois que CSS change et que vous êtes bon. Le navigateur ne rechargera vos fichiers que lorsqu'ils seront modifiés. Si vous faites un montage et que vous l'annulez, il n'y a pas de mal à trouver la version à laquelle vous devez revenir pour que vos visiteurs ne se téléchargent pas.


90
2017-09-23 03:21



Je ne sais pas pourquoi les gars prennent autant de peine à implémenter cette solution.

Tout ce que vous devez faire si vous obtenez l'horodatage modifié du fichier et l'ajouter en tant que chaîne de requête au fichier

En PHP je le ferais comme:

<link rel="stylesheet" href="mycss.css?v=<?php echo filemtime('mycss.css') ?>"/>

filemtime est une fonction PHP qui renvoie l'horodatage modifié du fichier.


57
2017-09-23 06:02



Vous pouvez juste mettre ?foo=1234 à la fin de votre importation css / js, changer 1234 pour être ce que vous voulez. Jetez un oeil à la source SO html pour un exemple.

L'idée là étant que le? les paramètres sont de toute façon rejetés / ignorés sur la requête et vous pouvez modifier ce nombre lorsque vous déployez une nouvelle version.


Remarque: Il y a un argument en ce qui concerne exactement comment cela affecte la mise en cache. Je crois que l'essentiel est que les requêtes GET, avec ou sans paramètres devrait être cachable, donc la solution ci-dessus devrait fonctionner.

Cependant, c'est au serveur Web de décider s'il veut adhérer à cette partie de la spécification et au navigateur utilisé par l'utilisateur, car il peut tout simplement aller de l'avant et demander une nouvelle version de toute façon.


50
2017-08-05 16:55



J'ai entendu cela appelé "versioning automatique". La méthode la plus courante consiste à inclure le mtime du fichier statique quelque part dans l'URL et à le supprimer à l'aide de gestionnaires de réécriture ou de config d'URL:

Voir également:


37
2018-06-24 23:20



La trentaine de réponses existantes sont d'excellents conseils pour un site web circa 2008. Cependant, quand il s'agit d'un moderne, application d'une seule page (SPA), il serait peut-être temps de repenser certaines hypothèses fondamentales ... en particulier l'idée qu'il est souhaitable que le serveur web ne serve que la version la plus récente d'un fichier.

Imaginez que vous êtes un utilisateur qui a une version M d'un SPA chargé dans votre navigateur:

  1. Votre pipeline de CD déploie la nouvelle version N de l'application sur le serveur
  2. Vous naviguez dans le SPA, ce qui envoie un XHR au serveur pour obtenir /some.template
    • (Votre navigateur n'a pas actualisé la page, vous utilisez donc toujours la version M)
  3. Le serveur répond avec le contenu de /some.template - Voulez-vous qu'il renvoie la version M ou N du modèle?

Si le format de /some.template changé entre les versions M et N (ou le fichier a été renommé ou autre) vous ne voulez probablement pas de version N du modèle envoyé au navigateur qui exécute l'ancienne version M de l'analyseur

Les applications Web rencontrent ce problème lorsque deux conditions sont remplies:

  • Les ressources sont demandées de manière asynchrone après le chargement initial de la page
  • La logique de l'application suppose des choses (susceptibles de changer dans les futures versions) sur le contenu de la ressource

Une fois que votre application a besoin de servir plusieurs versions en parallèle, résoudre la mise en cache et "recharger" devient trivial:

  1. Installez tous les fichiers de site dans les répertoires versionnés: /v<release_tag_1>/…files…, /v<release_tag_2>/…files…
  2. Définir les en-têtes HTTP pour permettre aux navigateurs de mettre les fichiers en cache pour toujours
    • (Ou mieux encore, tout mettre dans un CDN)
  3. Tout mettre à jour <script> et <link> tags, etc. pour pointer vers ce fichier dans l'un des répertoires versionnés

Cette dernière étape semble délicate, car elle peut nécessiter l'appel d'un générateur d'URL pour chaque URL de votre code côté serveur ou côté client. Ou vous pouvez simplement faire un usage intelligent de la <base> marque et changez la version actuelle en un seul endroit.

† Une façon de contourner ceci est d'être agressif pour forcer le navigateur à tout recharger quand une nouvelle version est publiée. Mais pour que les opérations en cours puissent se terminer, il peut être plus facile de prendre en charge au moins deux versions en parallèle: v-current et v-previous.


19
2017-09-23 03:23



N'utilisez pas foo.css? Version = 1! Les navigateurs ne sont pas censés mettre en cache les URL avec des variables GET. Selon http://www.thinkvitamin.com/features/webapps/serving-javascript-fast, bien que IE et Firefox ignorent cela, Opera et Safari ne le font pas! Au lieu de cela, utilisez foo.v1234.css et utilisez les règles de réécriture pour supprimer le numéro de version.


14
2017-09-23 13:54



Le RewriteRule a besoin d'une petite mise à jour pour les fichiers js ou css qui contiennent une notation par points à la fin. Par exemple. json-1.3.js.

J'ai ajouté une classe de négation par points [^.] À la regex donc .number. est ignoré.

RewriteRule ^(.*)\.[^.][\d]+\.(css|js)$ $1.$2 [L]

9
2017-09-24 11:38