Quand un bloqueur de traqueurs rend un site inaccessible — Diagnostic et correction

Crédit : Photo de Liana Ssur Unsplash

Les extensions de blocage de traqueurs font partie des outils de protection courants des utilisateurs. Elles opèrent généralement en filtrant silencieusement des requêtes vers des domaines tiers connus — publicités, scripts d’analyse, CDN. La plupart du temps, cela ne perturbe pas la navigation. Mais lorsqu’un site s’appuie sur l’un de ces domaines pour charger un composant critique de son interface, les effets du blocage peuvent dépasser largement ce qui était attendu.

C’est précisément ce qui s’est produit ici : sur Firefox avec une extension de blocage active, aucun lien ni bouton sur l’ensemble de la page ne répond aux clics. Le site est inutilisable. Sur les navigateurs basés sur Chrome, en revanche, tout fonctionne normalement.

Pour l’utilisateur, la solution peut être simple : désactiver l’extension concernée. Mais en contrepartie, cela l’expose. La vraie correction appartient au site.

Le composant en cause est une modale de consentement aux cookies pilotée par Vue.js. L’ensemble — overlay, formulaire, bouton — est déclaré dans le HTML de la page et rendu dynamiquement côté client. Le framework est chargé depuis un CDN public, et la visibilité de l’overlay est entièrement gérée par une directive Vue. C’est précisément cette dépendance externe qui crée la fragilité.

Architecture initiale

Vue.js est chargé depuis un CDN tiers, et toute la logique de l’overlay repose sur trois éléments qui s’articulent.

Le chargement du framework

<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>

L’overlay dans le template

L’overlay est un div à position fixe couvrant toute la fenêtre. Sa présence dans le DOM est pilotée par la directive v-if : Vue insère ou retire l’élément selon la valeur de saved. Sans background, il est invisible à l’œil.

<div v-if="! saved || displayed" class="layout">
    <!-- modale de consentement -->
    <button type="button" @click="onSavePresets">Enregistrer mes choix</button>
</div>
.layout {
    position: fixed;
    top: 0; left: 0;
    z-index: 100000;
    width: 100vw; height: 100vh;
    /* pas de background-color */
}

La logique de contrôle

L’état de l’overlay est piloté par une variable réactive saved, initialisée à true. Au chargement du composant (hook created), Vue interroge le localStorage pour savoir si l’utilisateur a déjà exprimé ses préférences :

  • aucune préférencesaved passe à false, le v-if devient true, Vue insère l’overlay.
  • préférences présentessaved reste true, le v-if est false, l’overlay n’est jamais inséré dans le DOM.
created: function () {
    if (localStorage.getItem('cookieConsent')) {
        // Visite suivante : préférences déjà enregistrées, overlay masqué
        this.presets = JSON.parse(localStorage.getItem('cookieConsent'));
    } else {
        // Première visite : saved = false → v-if actif → overlay inséré dans le DOM
        this.saved = false;
        this.presets = { analytics: false };
    }
},

Lorsque l’utilisateur valide ses choix, onSavePresets persiste les préférences dans le localStorage et repasse saved à true — Vue retire immédiatement l’overlay du DOM.

onSavePresets() {
    localStorage.setItem('cookieConsent', JSON.stringify(this.presets));
    this.saved = true;      // v-if devient false → overlay retiré du DOM
    this.displayed = false;
}

Tout repose donc sur un seul prérequis : que Vue soit disponible pour interpréter v-if, @click, et mettre à jour le DOM en réaction aux changements d’état.

Diagnostic

Étape 1 — Identifier l’élément bloquant

Le symptôme — aucun clic ne passe — oriente vers un élément qui intercepte les événements souris avant qu’ils atteignent leur cible. Le réflexe est d’ouvrir l’inspecteur des outils de développement du navigateur et d’utiliser le sélecteur d’éléments (l’icône de curseur en haut à gauche du panneau) pour cliquer directement sur la zone supposément cliquable. L’inspecteur met alors en surbrillance l’élément qui se trouve réellement sous le pointeur.

C’est ainsi qu’on identifie le div.layout : toujours présent dans le DOM, quelle que soit la valeur de saved. Dans l’onglet Styles, on constate qu’il n’a pas de background-color — il est donc invisible à l’œil — mais ses règles de positionnement sont bien là : position: fixed, width: 100vw, height: 100vh, z-index: 100000. Il recouvre silencieusement l’intégralité de la page et absorbe tous les événements souris sans rien laisser passer en dessous.

Étape 2 — Comprendre pourquoi v-if ne s’applique pas

La directive v-if="!saved || displayed" est une instruction Vue. Si Vue n’est pas chargé, elle n’est pas interprétée : c’est un attribut HTML ordinaire, sans effet sur le rendu. Le div.layout est alors toujours rendu par le navigateur, inconditionnellement.

Étape 3 — La cause racine

L’étape 2 établit que Vue n’est pas actif. La question devient : pourquoi ? Les outils de développement ne donnent pas de réponse évidente — pas d’erreur Vue is not defined en console, pas de requête marquée comme bloquée dans l’onglet Réseau. Le blocage opère en amont, silencieusement.

C’est la méthode d’exclusion qui mène à la réponse : désactiver l’extension et recharger la page restaure immédiatement le comportement normal. La réactiver le rompt à nouveau. L’extension est la variable. En croisant avec la liste des ressources chargées, on identifie que Vue.js — servi depuis cdn.jsdelivr.net, un domaine tiers connu — est la ressource absente.

L’extension de blocage de traqueurs intercepte la requête vers cdn.jsdelivr.net. Vue n’est jamais disponible. Aucune directive (v-if, @click, :open…) n’est traitée.

Résultat :

  • L’overlay est toujours dans le DOM (transparent, bloquant tous les clics).
  • Le bouton « Enregistrer mes choix » n’a aucun handler.
  • Le site est totalement inutilisable.

Sur Chrome, la page est pleinement fonctionnelle quelle que soit la situation : première visite ou non, la fenêtre de gestion des cookies s’affiche, l’utilisateur peut valider ses choix, et tous les liens de la page sont disponibles. Ce n’est pas une question de code — le code est identique. C’est une différence propre à Firefox : l’extension de blocage y est active et intercepte la requête vers le CDN. Il ne s’agit pas d’un bug à corriger mais d’un environnement spécifique à contourner.

La correction

La correction s’articule en trois couches complémentaires.

Servir Vue.js localement

Ne plus dépendre d’un CDN susceptible d’être bloqué. Un fichier servi depuis le même domaine que l’application ne peut pas être filtré par une règle de blocage par domaine tiers.

<!-- Avant -->
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.min.js"></script>

<!-- Après -->
<script src="/assets/vue.min.js"></script>

C’est la correction principale. Elle résout le problème pour l’immense majorité des cas.

Inverser l’état par défaut de l’overlay

Même avec Vue en local, un bloqueur plus agressif pourrait encore intervenir. La défense en profondeur consiste à faire en sorte que l’overlay soit inoffensif sans JavaScript : caché par CSS, et affiché uniquement si JS en décide explicitement.

/* Avant */
.layout {
    position: fixed;
    top: 0; left: 0;
    z-index: 100000;
    width: 100vw; height: 100vh;
}

/* Après */
.layout {
    display: none;           /* caché par défaut — aucun JS, aucun blocage */
    position: fixed;
    top: 0; left: 0;
    z-index: 100000;
    width: 100vw; height: 100vh;
    background-color: rgba(0, 0, 0, 0.5);
}

La directive v-if (qui ajoute/retire l’élément du DOM) est remplacée par un binding de style inline, qui agit sur la propriété display directement. Un style inline a une priorité CSS supérieure à toute règle de feuille de style — il écrase donc le display: none du CSS :

<!-- Avant -->
<div v-if="! saved || displayed" class="layout">

<!-- Après -->
<div :style="{ display: (! saved || displayed) ? 'block' : 'none' }" class="layout">

Sans Vue : le style :style="..." est un attribut ignoré, le CSS display: none s’applique — l’overlay est invisible et ne bloque rien. Avec Vue : :style injecte display: block en style inline quand la modale doit s’afficher.

Fallback vanilla JS

Si Vue ne charge toujours pas (cas extrême), l’utilisateur ne verrait jamais la modale de consentement. Un script vanilla placé en dehors du composant Vue (donc hors de sa portée de rendu) prend le relais :

(function () {
    var KEY = 'cookieConsent';
    var getLayout = function () {
        return document.querySelector('.layout');
    };

    // Affiche l'overlay si aucun consentement enregistré
    try {
        if (!localStorage.getItem(KEY)) {
            document.addEventListener('DOMContentLoaded', function () {
                var el = getLayout();
                if (el) el.style.display = 'block';
            });
        }
    } catch (e) {
        // localStorage peut être bloqué (navigation privée, mode strict)
        document.addEventListener('DOMContentLoaded', function () {
            var el = getLayout();
            if (el) el.style.display = 'block';
        });
    }

    // Ferme l'overlay au clic sur le bouton via délégation d'événement
    document.addEventListener('click', function (e) {
        var target = e.target;
        while (target && target !== document) {
            if (target.tagName === 'BUTTON' &&
                target.closest && target.closest('.layout')) {
                try {
                    var analytics = document.getElementById('analytics');
                    localStorage.setItem(KEY, JSON.stringify({
                        analytics: analytics ? analytics.checked : false
                    }));
                } catch (ex) {}
                var el = getLayout();
                if (el) el.style.display = 'none';
                return;
            }
            target = target.parentNode;
        }
    });
})();

La délégation d’événement sur document garantit que le handler survit aux re-rendus éventuels de Vue. Les try/catch couvrent les cas où localStorage lève une exception.

Quand Vue est présent, les deux systèmes coexistent sans conflit : @click="onSavePresets" et le listener vanilla appellent tous les deux el.style.display = 'none' — fermer deux fois un élément déjà fermé est sans conséquence.

Ce qu’il faut retenir

Un overlay plein écran sans background-color est un piège classique. Invisible à l’œil, il capture silencieusement tous les événements souris. Sans outillage de debug (vue en couches dans l’inspecteur, test de pointer-events), le symptôme — « les clics ne fonctionnent plus » — n’oriente pas naturellement vers un élément CSS.

v-if (et ses équivalents dans les autres frameworks) est une instruction du framework, pas du navigateur. Le HTML brut rendu par le serveur ne connaît pas Vue. Sans le framework, v-if="condition" est un attribut arbitraire. L’état « sans JavaScript » doit toujours être pensé explicitement.

Les ressources critiques au bon fonctionnement de l’interface ne doivent pas dépendre de CDN tiers. La décision de bloquer cdn.jsdelivr.net, cdnjs.cloudflare.com ou ajax.googleapis.com appartient à l’utilisateur — et elle est légitime. Une application robuste ne doit pas s’en remettre à leur disponibilité.

Une question ou un projet ?

Je suis disponible pour en discuter.

Me contacter