Forcer le navigateur client à recharger son cache des fichiers CSS et JS

Aujourd’hui j’ai été confronté à un problème très connu mais assez rarement pointé du doigt dans un contexte de production. Pour ma part je n’avais jamais eu l’occasion de m’y confronter… mais il n’en reste pas moins que ce qui suit mérite vraiment que l’on s’y intéresse, quel que soit le projet.

Lors d’une mise à jour majeure de la homepage d’un client, CSS et JS, les visiteurs qui avaient visité le site la veille ont eu une drôle de surprise sur la nouvelle page d’accueil : leur navigateur ayant gardé en cache une partie des feuilles de style et javascript, la home ne ressemblait à RIEN, à moins de faire un Ctrl+F5. Le client était assez mécontent (on le comprend) et me demandait si une solution existait.

J’ai donc cherché une solution à ce problème (sans trop y croire au début), problème qui ne se présente certainement que si on ne fait pas appel à un CMS, ce qui était mon cas. La seule solution est que les noms de fichiers changent lors de mises à jour des feuilles de styles et javascript. Certains choisissent de le changer à la main, d’autre de passer un nombre aléatoire en paramètre de l’url des fichiers… Mais la meilleure solution que j’ai trouvé consiste à ajouter un timestamp aux fichiers CSS et JS correspondant à la date de modification du fichier. Et pas besoin de faire ça à la main! Avec un peu de PHP et de la réécriture d’url, la solution est toute simple, ingénieuse et très efficace. Pour les détails, je vous laisse consulter cet article très complet trouvé sur websourcing.fr.

Inconvénients de la solution

Après un petit test sur le site du client, cette solution pose tout un tas de problèmes :

  • le .htaccess fournit ne permet pas d’avoir des fichiers ailleurs que dans des dossiers css/ et js/ ;
  • les fichiers js comportant des chiffres, comme les plugin jQuery (UI) par exemple qui affichent leur numéro de version, ne sont plus retrouvés à cause du .htaccess. Les noms de fichiers posent donc en général trop de problèmes ;
  • la fonction filemtime() génère un certain nombre d’erreurs PHP que je n’ai pas pu expliquer.

Les deux premiers problèmes pourraient être réglés en améliorant le htaccess. Mais nous ne sommes pas tous capables d’écrire de précises RegExp dans un htaccess pour ce genre de comportement. Je ne le suis d’ailleurs pas moi-même. Mais surtout, il n’est pas bon d’appuyer un fonctionnement si important tel que l’appel de nos CSS et JS sur un htaccess.

Solution alternative

J’ai donc opté pour une solution intermédiaire plus simple qui consiste à :

  • Intégrer la date du jour sous forme « jjmm » dans l’URL grâce à la fonction PHP date(), au lieu de la date de modification du fichier. Cela évitera les erreurs possibles avec la fonction PHP filemtime().
  • Passer la date « jjmm » dans un paramètre d’URL au lieu de l’ajouter au nom du fichier. Ceci nous dispense d’utiliser le .htaccess.

Le premier petit inconvénient est que le fichier est forcément rechargé une fois par jour. Le second est que si vous faites une modification et mise en ligne en cours de journée, le cache de vos visiteurs ne verra pas de changement dans le nom. L’avantage est que la syntaxe est plus simple côté fonction PHP (cf exemple plus bas).

Petit exemple :


<link rel="stylesheet" type="text/css" href="/css/style.css?d=<?php echo date('dm'); ?>" media="screen" />

La solution est exploitable en contexte de production, elle n’est pas lourde dans le code et est assez optimisée pour de bonnes performances de navigation. Elle peut donc être appliquée à long terme. Il n’y a plus qu’à l’oublier et les visiteurs verront toujours votre site à jour (si tenté que vous ayez fait une release pendant la nuit).

17 commentaires
  1. OlivierSC dit :

    Le CMS TYPO3 intègre un système équivalent pour la liaison de toutes les ressources CSS et JS.

    Avant chaque mise en cache des pages, la date de modification des fichiers est calculée et intégrée dans l’appel de ces ressources.

    Après avoir mis à jour une ressource, il suffit de vider les caches et le processus recommence.

    La nouvelle date de modification est alors calculée et intégrée dans les liens forçant les internautes à récupérer automatiquement la nouvelle version des fichiers.

  2. Matthieu R. dit :

    Merci Olivier, en effet plusieurs CMS intègrent des fonctionnalités de ce type pour éviter les problèmes de cache. Dans mon cas j’étais sur une plate-forme développée sur-mesure (et assez ancienne même), donc pas de CMS 😉

  3. Franck W. dit :

    Effectivement très pratique en période de production lorsque les modifications de styles sont rapprochées. Merci bcp pour l’astuce. Des outils existent pour la mise a jour automatique du cache de vue et de page mais pour les fichiers css ils ne fonctionnaient pas pour moi. Encore merci.

  4. Robert dit :

    Franchement Merci bcp du conseil, cela me sera très efficace car je suis en pleine periode de livraison.

  5. m$x dit :

    Bonsoir,

    je déconseille vivement de passer une date en url !
    Il existe en effet des problèmes de caches qui peuvent être résolu facilement avec un bon htacces.

    Je recommande d’ailleurs le lecture de l’excellent tutoriel du site du zero sur le htacces et le htpassword. C’est vraiment à la portée de tous donc n’ayez pas peur 😉

    Pour les erreurs Php, il faut les afficher à l’aide d’un petit echo.
    Le code erreur retourné permet de résoudre ces problèmes.

    Pourquoi il ne faut pas toucher à l’url ?
    Parce que les nouvelles règle de SEO introduite par Google Panda font que votre site va littéralement perdre en Rank car le robot de la firme américaine pensera que c’est un site mal conçus ou défaillant.

    J’espère vous avoir aidé.

    Cordialement

  6. Matthieu dit :

    Bonjour m$x,
    Merci pour ton commentaire, j’aimerais consulter la ressource dont tu parles, sur le site du zéro, avant de répondre. Peux tu m’indiquer l’adresse?
    Merci d’avance!

  7. Kevin dit :

    Pourquoi ne pas utiliser un numéro de version plutôt que la date ?

    On se fait une fonction get_version() qui renvoi la version en cours (en se basant sur une variable incrémenté à la main, sur un numéro de révision svn, sur la date d’un fichier, …)

    l’avantage c’est que le cache est rechargé uniquement lorsque c’est nécessaire et pas tous les jours et surtout on peut changer 50 fois le site dans la même journée, du moment que get_version() renvoi une valeur différent c’est bon.

  8. Rémi dit :

    Et, si possible, pourquoi ne pas rajouter non pas date(‘dm’) comme paramètre, mais bien

    /js/monfichier.js?_

    Comme cela, il affichera la date de modification du fichier.
    L’inconvénient, c’est qu’il faut renseigner systématiquement le chemin complet du fichier, mais on peut automatiser tout cela assez simplement.

  9. Rémi dit :

    (balise PHP non passée)
    echo @filemtime(ROOT . ‘/js/monfichier.js’);

  10. Light dit :

    Deux ans après, je crois que tu t’es un peu compliqué la vie parce que tu pouvais forcer le cache du navigateur à se vider en PHP :

    header(« Expires: Tue, 15 Jan 2013 08:16:45 GMT »);
    header(« Last-Modified: Tue, 15 Jan 2013 08:16:45 GMT »);
    header(« Cache-Control: no-cache, must-revalidate »);
    header(« Pragma: no-cache »);

  11. Matthieu dit :

    Je ne trouve pas que ma solution soit plus compliquée que la tienne. Sans vouloir troller, tous les CMS fonctionnent avec un système de « token » en paramètre des URLs des ressources pour gérer facilement le cache côté client. C’est un design pattern commun, simple et léger, à mon avis.
    Cependant, le plus souvent il ne s’agit pas d’une chaine de date comme évoqué dans cet article, mais bon…

    Arrête moi si je me trompe mais, la solution du header en PHP fonctionnerait sur une page PHP… mais pour du CSS ? Et quand bien même cela fonctionnerait en appliquant ceci sur la page qui appelle les CSS, l’intégralité des ressources de la page, y compris les images donc, ne seraient du coup pas mises en cache ? Un peu chaud non ?

    Quant à la solution du htaccess je ne la trouve pas particulièrement adaptée non-plus, pour les raisons suivantes :
    1. Il faut retoucher le htaccess à chaque fois que l’on veut un comportement de mise en cache différent. Je ne vais pas m’étendre sur les multitudes de cas pour lesquels ce serait inadapté.
    2. Moins on en met dans un htaccess, mieux on se porte. Le serveur va chercher et analyser récursivement les htaccess dans tous les répertoires si AllowOverride est à autre chose que « None », à éviter donc.
    3. Les directives de cache similaires à celle du htaccess devraient être données directement dans les configuration d’Apache, mais là on perd encore un peu plus en flexibilité.

    J’ai demandé la source des infos de la personne qui a posté au sujet du htaccess car à mon avis l’argument de Google Panda (Pingouin maintenant) n’est pas valable. Cela m’étonnerait beaucoup que Google se soucie de la façon dont on appelle nos ressources CSS/JS, ça n’a pas de sens. Je n’ai en tout cas rien trouvé qui évoquait cela. Et de toutes façons, je le répète, les CMS fonctionnent ainsi.

  12. Lamis dit :

    Merci Mattew

    Pour moi votre solution est plus simple et efficace

    merci encore

  13. LaurentB dit :

    Salut Mathieu,

    m$x confond un peu tout.
    Rien à voir avec le Panda car c’est depuis toujours un problème de changer d’URL. Pour info, le moteur arrête de suivre après 5 redirections 301.
    Il suffit de mettre ta variable de date dans l’URL après l’annulateur dièse #. Les moteurs s’arrêtent là et cela éradique le souci des changements incessants d’URLs.

  14. Luis dit :

    Bonjour.
    Merci pour la précision Laurent, il me semblait bien que m$x n’était pas tout à fait dans le vrai.

    Sinon, pour info « orthographique », il me semble qu’on écrit « Si tant est que… »

  15. Salut, superbe astuce, j’utilise plutôt date(‘Hi’) comme ça ça réagit même si je met à jour plusieurs fois par jour 😉

  16. Yolanda be cool ! dit :

    Merci d’avoir testé ça pour nous et d’avoir partagé l’info.
    Bonne continuation.

    P.S : j’ai adoré le « si tenté » à la fin (au lieu de « si tant est »)… 😉

  17. michel dit :

    Une solution derivee de celle presente ici est:
    1/ recuperer la date de modification du fichier et la stocker dans une session (pour eviter les appels a chaque fois que l’on souhaite faire appel au fichier js).
    $_SESSION[« js_ext_name »]=date(« Ymd », filemtime(« Book/Booking.js »));
    2/ Sur son serveur on installe 2 fichiers:
    a/ le fichier js original (monFichier.js) qui sert a ce que la fonction filemtime puisse trouver la date de modif.
    b/ le fichier js original mais qui se nomme cette fois monFichierDateDeDerniereModif.js
    3/Ensuite dans ses pages html ou php,on appelle son fichier js (monFichierDateDeDerniereModif.js) grace a la date de modif stockee en session, monFichierjs.$_SESSION[« js_ext_name »].js
    Cette facon de proceder est que le navigateur n’a pas a recharger le fichier tous les jours, seulement lors des modifications.

Laisser une réponse