CookieDetox L'Observatoire Legal-Tech
Sanctions & Amendes 2026-09-19

Nuxt 3 & Vue.js : Consent Mode v2 et blocage conditionnel des scripts tiers

CD

Par Cellule Investigation CookieDetox

Expertise Juridique & Conformité

🔗
T

L'essentiel à retenir (En bref)

Dans Nuxt 3, la conformité RGPD exige la lecture synchrone de l'état du consentement via useCookie côté SSR afin d'éliminer les erreurs d'hydratation Vue. Les signaux par défaut du Google Consent Mode v2 ('denied') doivent être injectés dans nuxt.config.ts avant l'exécution de GTM. Les scripts tiers doivent être bloqués via le composable useScript ou un store Pinia réactif avant tout consentement préalable positif.

1. Synthèse technique exécutive & Le paradoxe d'hydratation SSR

L'implémentation de bandeaux de consentement au sein d'applications monopages (SPA) et de frameworks avec rendu côté serveur (SSR) tels que Nuxt 3 soulève un défi d'ingénierie critique : la concurrence d'exécution entre la génération HTML serveur, l'hydratation client et le déclenchement asynchrone des traceurs et cookies tiers. En vertu de l'article 5(3) de la directive ePrivacy ↗ 2002/58/CE (modifiée par la directive 2009/136/CE) et de l'article 7 du RGPD ↗, aucun traceur non strictement nécessaire ni empreinte de terminal (device fingerprinting) ne peut être déposé ou lu avant le recueil d'un consentement préalable positif (opt-in), libre, spécifique, éclairé et univoque.

Dans les implémentations Vue 3 conventionnelles, les développeurs conditionnent souvent l'affichage de la bannière dans le hook onMounted() en interrogeant le localStorage ou un cookie purement côté client. Cette méthode engendre deux défaillances majeures : un décalage d'agencement visuel pénalisant (Cumulative Layout Shift - CLS) couplé à des avertissements d'incohérence d'hydratation (Hydration mismatch : Mismatching childNodes vs. VNode), ou pire, une infraction caractérisée où les balises de tracking injectées via GTM s'exécutent dès l'initialisation avant que l'état réactif ne puisse évaluer le choix de l'utilisateur. En mode SSG (génération statique) ou pré-rendu, intégrer des tags traceurs dans le payload statique transmet l'adresse IP et le user-agent des visiteurs aux serveurs de Google, Meta ou TikTok avant même la moindre hydratation client.

Afin de garantir une conformité technique et juridique irréprochable, l'architecture Nuxt 3 doit mobiliser la lecture synchrone des cookies côté SSR via useCookie, injecter les signaux 'denied' par défaut du Google Consent Mode v2 directement dans l'en-tête du document avant toute bibliothèque de suivi, et piloter le chargement dynamique des scripts fournisseurs via les composables natifs useScript ou useHead.

2. Schéma d'architecture : État SSR, Pinia & Google Consent Mode v2

L'architecture fondamentale repose sur l'évaluation du statut de consentement par le moteur Nitro sur le serveur avant la transmission du flux de réponse au navigateur. Cette approche supprime tout sautillement d'hydratation et garantit que les états par défaut du Google Consent Mode v2 (CoM v2) sont initialisés inline en amont du conteneur Google Tag Manager (GTM).

Étape 1 : Initialisation des valeurs par défaut du Consent Mode v2 dans nuxt.config.ts

Injectez le script d'initialisation inline directement dans la configuration nuxt.config.ts. Les paramètres ad_storage, analytics_storage, ad_user_data et ad_personalization doivent impérativement être définis sur 'denied' lors du premier chargement.

// nuxt.config.ts
export default defineNuxtConfig({
  app: {
    head: {
      script: [
        {
          hid: 'consent-mode-v2-init',
          children: `
            window.dataLayer = window.dataLayer || [];
            function gtag(){dataLayer.push(arguments);}
            gtag('consent', 'default', {
              'ad_storage': 'denied',
              'analytics_storage': 'denied',
              'ad_user_data': 'denied',
              'ad_personalization': 'denied',
              'wait_for_update': 500
            });
            gtag('set', 'ads_data_redaction', true);
          `,
          type: 'text/javascript'
        },
        {
          hid: 'gtm-loader',
          children: `
            (function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
            new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
            j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
            'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
            })(window,document,'script','dataLayer','GTM-XXXXXXX');
          `,
          type: 'text/javascript'
        }
      ]
    }
  }
});

Étape 2 : Gestion réactive du consentement via Pinia et useCookie

Le statut est extrait côté serveur grâce à useCookie. Par défaut, useCookie sérialise et désérialise les valeurs pendant le rendu SSR et l'hydratation, assurant une parfaite concordance de l'arbre DOM sans aucune erreur d'hydratation.

// stores/consent.ts
import { defineStore } from 'pinia';

export interface ConsentCategories {
  analytics: boolean;
  marketing: boolean;
  functional: boolean;
}

export const useConsentStore = defineStore('consent', () => {
  const defaultConsent: ConsentCategories = {
    analytics: false,
    marketing: false,
    functional: false
  };

  // Durée de conservation de 6 mois conforme aux lignes directrices de la CNIL ↗
  const cookieConsent = useCookie<ConsentCategories | null>('cd_consent_state', {
    maxAge: 60 * 60 * 24 * 180,
    sameSite: 'lax',
    secure: true,
    watch: true
  });

  const consentGiven = computed(() => cookieConsent.value !== null);
  const categories = ref<ConsentCategories>(cookieConsent.value || defaultConsent);

  function syncWithGoogleConsentMode(state: ConsentCategories) {
    if (process.client && window.gtag) {
      window.gtag('consent', 'update', {
        'analytics_storage': state.analytics ? 'granted' : 'denied',
        'ad_storage': state.marketing ? 'granted' : 'denied',
        'ad_user_data': state.marketing ? 'granted' : 'denied',
        'ad_personalization': state.marketing ? 'granted' : 'denied'
      });
      window.dataLayer.push({
        event: 'cookie_consent_update',
        consent_analytics: state.analytics,
        consent_marketing: state.marketing
      });
    }
  }

  function setConsent(newPreferences: ConsentCategories) {
    categories.value = { ...newPreferences };
    cookieConsent.value = newPreferences;
    syncWithGoogleConsentMode(newPreferences);
  }

  function acceptAll() {
    setConsent({ analytics: true, marketing: true, functional: true });
  }

  function rejectAll() {
    setConsent({ analytics: false, marketing: false, functional: false });
  }

  return {
    categories,
    consentGiven,
    setConsent,
    acceptAll,
    rejectAll,
    syncWithGoogleConsentMode
  };
});

Étape 3 : Blocage des scripts tiers hors GTM via useScript

Pour charger des scripts externes (ex. Pixel Meta, Hotjar ou SDK publicitaires) en dehors de GTM, utilisez le composable Nuxt useScript ou montez conditionnellement les scripts selon l'état réactif du store Pinia.

// composables/useVendorTracking.ts
export function useVendorTracking() {
  const consentStore = useConsentStore();

  watch(
    () => consentStore.categories.marketing,
    (granted) => {
      if (granted && process.client) {
        useScript({
          src: 'https://connect.facebook.net/en_US/fbevents.js',
          async: true
        });
      }
    },
    { immediate: true }
  );
}

3. Matrice des risques réglementaires & juridiques : Conformité SPA & SSR

Les autorités de protection des données (CNIL en France, AEPD, DPC) imposent des exigences d'audit strictes sur l'architecture technique du consentement. Le tableau ci-dessous confronte les pratiques d'ingénierie non conformes aux exigences de niveau forensique requises par le RGPD et la directive ePrivacy ↗.

Vecteur ArchitecturalPratique Non ConformeImplémentation Nuxt 3 ConformeRisque Juridique & Jurisprudence
État initial du consentementTraceurs exécutés au SSR ou lors du montage du composant ; bannière affichée a posteriori.Signaux Consent Mode v2 par défaut ('denied') injectés dans le head SSR ; scripts tiers bloqués.Violation de l'art. 5(3) ePrivacy ; sanctions RGPD art. 83(5) jusqu'à 20 M€ ou 4 % du CA (CJUE C-673/17 Planet49).
Hydratation et dérive d'étatLecture du localStorage dans onMounted(), induisant un flash visuel et des courses d'exécution.Lecture serveur via useCookie() au sein du cycle SSR ; hydratation synchrone du payload.Violation de l'art. 7(1) RGPD : incapacité à démontrer la preuve et jeton de consentement horodaté lors du premier contact.
Activation Consent Mode v2Omission de ad_user_data et ad_personalization, ou injection directe de GA4 sans signaux.Configuration stricte des 4 paramètres obligatoires avant l'exécution du conteneur GTM.Suspension des comptes publicitaires Google en vertu du DMA (Digital Markets Act) et invalidation des flux marketing.
Symétrie de refusBouton 'Tout accepter' au premier niveau ; refus relégué dans des sous-menus complexes.Boutons 'Tout accepter' et 'Tout refuser' présentés avec une stricte équivalence ergonomique et visuelle.Délibérations CNIL n° 2020-091 et 2020-092 ; jurisprudence CJUE Fashion ID (C-40/17) sur la responsabilité conjointe.
Génération statique (SSG)Compilation de scripts traceurs en dur directement dans les bundles HTML statiques.Hydratation conditionnelle des scripts tiers sur le client uniquement après vérification du store réactif.Transferts illicites d'adresses IP européennes vers des tiers hors UE sans mécanismes conformes aux articles 44 à 49 du RGPD.

4. Guide d'implémentation pas à pas & Protocole de vérification forensique

Pour vérifier qu'une architecture Nuxt 3 résiste à un contrôle inopiné de l'autorité de protection des données (CNIL en France, AEPD, DPC), appliquez ce protocole d'audit pas à pas via les outils de développement Chromium (DevTools).

Étape de contrôle 1 : Analyse des flux réseau au premier chargement

  1. Ouvrez une session de navigation privée vierge et lancez les DevTools (F12). Rendez-vous sur l'onglet Réseau (Network).
  2. Filtrez les requêtes avec les chaînes google-analytics.com, doubleclick.net ou les domaines de vos régies (ex. connect.facebook.net).
  3. Chargez l'application Nuxt 3. Contrôlez les appels sortants :
    • Aucune requête vers les points de terminaison collect ne doit survenir avant l'action de l'utilisateur.
    • En mode Consent Mode v2 'Basic', aucun tag GTM ne doit être déclenché. En mode 'Advanced', assurez-vous que les pings de modélisation comportent le paramètre &gcs=G100 (Analytics et Publicité refusés) sans aucun identifiant client ni cookie dans les en-têtes.

Étape de contrôle 2 : Validation de la séquence Consent Mode v2 dans la Console

Vérifiez que la pile d'événements du dataLayer respecte la chronologie réglementaire. Exécutez le script suivant dans la console :

console.table(
  dataLayer.filter(item => item[0] === 'consent' || item.event === 'cookie_consent_update')
);

L'affichage doit impérativement respecter la séquence suivante :

  1. consent, default avec l'ensemble des 4 variables définies sur denied (Horodatage initial : 0 ms).
  2. Initialisation du conteneur gtm.js.
  3. Interaction utilisateur (ex. clic sur 'Tout accepter').
  4. consent, update passant les paramètres pertinents à granted.
  5. Événement déclencheur personnalisé cookie_consent_update.

Étape de contrôle 3 : Validation de la durée de vie du cookie et absence d'erreur d'hydratation

Dans l'onglet Application, sous la section Stockage > Cookies, examinez cd_consent_state :

  • Date d'expiration (Expires / Max-Age) : Ne doit pas dépasser 6 mois (recommandation stricte de la CNIL ; 13 mois maximum selon les juridictions).
  • Attribut Secure : Doit être obligatoirement défini sur true.
  • Attribut SameSite : Doit être configuré sur Lax ou Strict.
  • Assurez-vous qu'aucun message [Vue warn]: Hydration node mismatch n'apparaît dans la console lors d'un rechargement complet.

5. Verdict stratégique & Recommandations zéro-sanction pour les marques européennes

Injecter des scripts de suivi tiers dans des applications Nuxt 3 à fort trafic sans isolation stricte de l'état expose l'organisation à un risque de sanction financière lourd. Les CMP tierces qui reposent uniquement sur l'injection de scripts côté client échouent systématiquement face aux exigences de l'audit forensique en raison des asynchronismes du SSR.

Les directions techniques et les DPO doivent formaliser une architecture articulée autour de trois impératifs :

  • Extraction de l'état du consentement SSR-first : Mobilisation impérative de useCookie au niveau de Nitro pour éliminer les décalages de mise en page (CLS), éradiquer les bugs de réconciliation d'hydratation et transmettre un état déterministe aux composants serveur.
  • Verrouillage par défaut du Consent Mode v2 : Injection inline des états 'denied' dès la compilation du document HTML dans nuxt.config.ts, interdisant à tout pixel tiers de lire les données du terminal de manière prématurée.
  • Chargement déclaratif et conditionnel des tiers : Conditionnement strict des tags publicitaires et analytiques aux composables Nuxt (useScript) ou à des déclencheurs GTM synchronisés sur l'événement cookie_consent_update.

Cette rigueur technique garantit la charge de la preuve exigée par l'article 7(1) du RGPD ↗, neutralise les risques de mise en demeure par la CNIL en application des délibérations n° 2020-091 et 2020-092, tout en préservant le score Core Web Vitals de vos applications de production.

§

Sources Officielles & Jurisprudence de Référence

Textes réglementaires primaires, délibérations officielles de la CNIL et arrêts de la CJUE cités dans cette analyse.

  • Curia / CJUE Arrêt CJUE Fashion ID (Affaire C-40/17 du 29 juillet 2019) : Co-responsabilité sur les plugins et pixels tiers
    Consulter le texte officiel
  • Curia / CJUE Arrêt CJUE Planet49 (Affaire C-673/17 du 1er octobre 2019) : Invalidité absolue des cases pré-cochées pour le consentement cookies
    Consulter le texte officiel
  • EUR-Lex Article 83 du Règlement (UE) 2016/679 (RGPD) — Conditions générales pour imposer des amendes administratives (plafonds de 20 M€ ou 4 % du CA mondial)
    Consulter le texte officiel
  • EUR-Lex Article 7 du Règlement (UE) 2016/679 (RGPD) — Conditions applicables au consentement et preuve du retrait
    Consulter le texte officiel
  • EUR-Lex Directive 2002/58/CE modifiée (Directive ePrivacy relative au traitement des données et à la protection de la vie privée dans le secteur des communications électroniques)
    Consulter le texte officiel
  • Légifrance / CNIL Délibération CNIL n° 2020-091 du 17 septembre 2020 portant adoption de lignes directrices relatives à l'application de l'article 82
    Consulter le texte officiel
Mis à jour le 2026-09-19
Partager cet article:

Questions Fréquentes (FAQ)

Pourquoi la lecture du consentement dans onMounted() provoque-t-elle des erreurs d'hydratation ?

Dans onMounted(), le serveur Nitro génère le HTML initial en présumant l'absence de consentement, tandis que le client altère le DOM dès la lecture de localStorage ou document.cookie. Cette divergence entre le DOM serveur et l'arbre VNode client déclenche les alertes Vue Hydration mismatch (erreurs 425/429) et provoque un décalage d'affichage pénalisant (CLS).

Comment configurer le Google Consent Mode v2 dans Nuxt 3 sans plugin tiers ?

Injectez un script inline dans le tableau app.head de nuxt.config.ts. Il initialise window.dataLayer et exécute gtag('consent', 'default', {...}) en fixant ad_storage, analytics_storage, ad_user_data et ad_personalization sur 'denied' avec wait_for_update, impérativement avant le script de chargement du conteneur GTM.

En quoi useCookie évite-t-il le décalage de mise en page (CLS) sur Nuxt 3 ?

useCookie intercepte l'en-tête HTTP Cookie dès la requête entrante sur le moteur Nitro. Le serveur effectue le rendu directement avec l'état masqué ou affiché du bandeau dans le flux HTML envoyé au navigateur, éliminant tout flash visuel et saut d'interface propre aux résolutions exécutées exclusivement côté client.

Quelle est la différence entre le mode Basic et Advanced du Consent Mode v2 ?

En mode Basic, les balises de tracking sont bloquées et ne transmettent aucune requête tant que le consentement n'est pas accordé. En mode Advanced, les balises se chargent immédiatement mais émettent des pings sans cookie (balisés &gcs=G100) pour la modélisation statistique, sans accéder au terminal de l'utilisateur.