Guide de mise en œuvre du consentement aux cookies et du SPA

Comprendre le défi

Les applications monopages (SPA) développées avec React, Vue, Angular ou d'autres frameworks posent des défis particuliers pour le consentement aux cookies de TermsFeed, car :

  1. Pas de rafraîchissement de la page. La page ne se recharge pas comme les pages web classiques.
  2. Contenu dynamique. Le contenu se charge dynamiquement sans déclencher à nouveau DOMContentLoaded.
  3. Changements de route. La navigation s'effectue via JavaScript, et non par le chargement traditionnel des pages.
  4. Gestion de l'état. L'état du consentement doit persister malgré les changements de route.

Solution : recharger lors de la navigation

Problème

« J'ai une application React SPA et la bannière de cookies ne s'affiche que lors du premier chargement de la page. Lorsque les utilisateurs naviguent vers d'autres pages, la bannière n'apparaît plus s'ils n'ont pas fait de choix. De plus, s'ils acceptent les cookies sur une page, les scripts ne se chargent pas sur les pages suivantes tant qu'il n'y a pas eu de rafraîchissement complet. »

Solution

Lors de la mise en œuvre de TermsFeed Cookie Consent dans une SPA, vous devez :

  1. Initialiser le consentement lors du premier chargement.
  2. Détecter les changements de route.
  3. Recharger la bannière si nécessaire.
  4. Vérifier à nouveau l'état du consentement.

Implémentation React

Étape 1 : Créer un composant Cookie Consent

// CookieConsent.js
import { useEffect } from 'react';
import { useLocation } from 'react-router-dom';

const CookieConsent = () => {
    const location = useLocation();

    useEffect(() => {
        // Load TermsFeed script if not already loaded
        if (!window.cookieconsent) {
            const script = document.createElement('script');
            script.src = '//www.termsfeed.com/public/cookie-consent/4.2.0/cookie-consent.js';
            script.charset = 'UTF-8';
            script.onload = () => initializeCookieConsent();
            document.head.appendChild(script);
        } else {
            // Script already loaded, just check if banner needs to be shown
            checkBannerState();
        }
    }, []); // Run once on mount

    useEffect(() => {
        // Check banner state on route change
        if (window.cookieconsent) {
            checkBannerState();
        }
    }, [location]); // Run on every route change

    const initializeCookieConsent = () => {
        if (window.cookieconsent && typeof window.cookieconsent.run === 'function') {
            window.cookieconsent.run({
                "notice_banner_type": "headline",
                "consent_type": "express",
                "palette": "light",
                "language": "en",
                "website_name": "My SPA",
                "website_privacy_policy_url": "https://mysite.com/privacy",
                "open_preferences_center_selector": "#changePreferences",
                "page_load_consent_levels": ["strictly-necessary"],
                "callbacks": {
                    "scripts_specific_loaded": (level) => {
                        console.log("Scripts loaded for: " + level);
                        // Re-initialize any components that need tracking
                        if (level === 'tracking') {
                            reinitializeAnalytics();
                        }
                    }
                }
            });
        }
    };

    const checkBannerState = () => {
        // Check if user has already made a consent choice
        const consentCookie = getCookie('cookie_consent_level');
        if (!consentCookie) {
            // No consent yet, banner should be visible
            // No action needed - banner will show automatically
        }
    };

    const getCookie = (name) => {
        const value = ; ${document.cookie};
        const parts = value.split(; ${name}=);
        if (parts.length === 2) return parts.pop().split(';').shift();
    };

    const reinitializeAnalytics = () => {
        // Re-run any analytics initialization needed
        if (window.gtag) {
            window.gtag('config', 'G-XXXXXXXXXX', {
                'page_path': location.pathname
            });
        }
    };

    return null; // This component doesn't render anything
};

export default CookieConsent;

Étape 2 : Ajouter au composant App

// App.js
import React from 'react';
import { BrowserRouter as Router, Routes, Route } from 'react-router-dom';
import CookieConsent from './components/CookieConsent';
import Home from './pages/Home';
import About from './pages/About';

function App() {
    return (
        <Router>
            <CookieConsent />
            <Routes>
                <Route path="/" element={<Home />} />
                <Route path="/about" element={<About />} />
            </Routes>

            {/ Preferences button /}
            <button id="changePreferences" style={{
                position: 'fixed',
                bottom: '20px',
                right: '20px',
                zIndex: 9999
            }}>
                Cookie Settings
            </button>

            <noscript>
                Free cookie consent management tool by <a href="https://www.termsfeed.com/">TermsFeed</a>
            </noscript>
        </Router>
    );
}

export default App;

Implémentation Vue.js

Étape 1 : Créer le plugin Cookie Consent

// plugins/cookieConsent.js
export default {
    install(app, options) {
        // Load script
        const loadCookieConsent = () => {
            if (window.cookieconsent) return Promise.resolve();

            return new Promise((resolve, reject) => {
                const script = document.createElement('script');
                script.src = '//www.termsfeed.com/public/cookie-consent/4.2.0/cookie-consent.js';
                script.charset = 'UTF-8';
                script.onload = () => resolve();
                script.onerror = () => reject();
                document.head.appendChild(script);
            });
        };

        // Initialize
        const initCookieConsent = () => {
            if (window.cookieconsent && typeof window.cookieconsent.run === 'function') {
                window.cookieconsent.run({
                    "notice_banner_type": "headline",
                    "consent_type": "express",
                    "palette": "light",
                    "language": "en",
                    "website_name": options.websiteName || "My Vue SPA",
                    "website_privacy_policy_url": options.privacyUrl || "#",
                    "open_preferences_center_selector": "#changePreferences",
                    "page_load_consent_levels": ["strictly-necessary"]
                });
            }
        };

        // Make available globally
        app.config.globalProperties.$cookieConsent = {
            load: loadCookieConsent,
            init: initCookieConsent
        };

        // Auto-initialize on mount
        if (typeof window !== 'undefined') {
            loadCookieConsent().then(() => {
                document.addEventListener('DOMContentLoaded', initCookieConsent);
            });
        }
    }
};

Étape 2 : Utiliser dans l'application principale

// main.js
import { createApp } from 'vue';
import { createRouter, createWebHistory } from 'vue-router';
import App from './App.vue';
import cookieConsentPlugin from './plugins/cookieConsent';

const router = createRouter({
    history: createWebHistory(),
    routes: [
        { path: '/', component: () => import('./views/Home.vue') },
        { path: '/about', component: () => import('./views/About.vue') }
    ]
});

const app = createApp(App);

app.use(router);
app.use(cookieConsentPlugin, {
    websiteName: 'My Vue App',
    privacyUrl: 'https://mysite.com/privacy'
});

app.mount('#app');

Étape 3 : Créer le composant App

<!-- App.vue -->
<template>
    <div id="app">
        <router-view />

        <button id="changePreferences" class="cookie-settings-btn">
            Cookie Settings
        </button>

        <noscript>
            Free cookie consent management tool by
            <a href="https://www.termsfeed.com/">TermsFeed</a>
        </noscript>
    </div>
</template>

<script>
export default {
    name: 'App',
    mounted() {
        // Watch for route changes
        this.$router.afterEach((to, from) => {
            // Check if consent banner needs to be re-shown
            this.checkConsentState();
        });
    },
    methods: {
        checkConsentState() {
            const consentCookie = document.cookie
                .split('; ')
                .find(row => row.startsWith('cookie_consent_level='));

            if (!consentCookie) {
                console.log('No consent given yet');
            }
        }
    }
};
</script>

<style>
.cookie-settings-btn {
    position: fixed;
    bottom: 20px;
    right: 20px;
    z-index: 9999;
    padding: 10px 20px;
    background: #007bff;
    color: white;
    border: none;
    border-radius: 5px;
    cursor: pointer;
}
</style>

Implémentation Angular

Étape 1 : Créer le service Cookie Consent

// services/cookie-consent.service.ts
import { Injectable } from '@angular/core';
import { Router, NavigationEnd } from '@angular/router';
import { filter } from 'rxjs/operators';

declare global {
    interface Window {
        cookieconsent: any;
    }
}

@Injectable({
    providedIn: 'root'
})
export class CookieConsentService {
    private scriptLoaded = false;

    constructor(private router: Router) {}

    initialize(): Promise<void> {
        return this.loadScript().then(() => {
            this.initializeConsent();
            this.watchRouteChanges();
        });
    }

    private loadScript(): Promise<void> {
        if (this.scriptLoaded || window.cookieconsent) {
            return Promise.resolve();
        }

        return new Promise((resolve, reject) => {
            const script = document.createElement('script');
            script.src = '//www.termsfeed.com/public/cookie-consent/4.2.0/cookie-consent.js';
            script.charset = 'UTF-8';
            script.onload = () => {
                this.scriptLoaded = true;
                resolve();
            };
            script.onerror = () => reject();
            document.head.appendChild(script);
        });
    }

    private initializeConsent(): void {
        if (window.cookieconsent && typeof window.cookieconsent.run === 'function') {
            window.cookieconsent.run({
                notice_banner_type: 'headline',
                consent_type: 'express',
                palette: 'light',
                language: 'en',
                website_name: 'My Angular App',
                website_privacy_policy_url: 'https://mysite.com/privacy',
                open_preferences_center_selector: '#changePreferences',
                page_load_consent_levels: ['strictly-necessary'],
                callbacks: {
                    scripts_specific_loaded: (level: string) => {
                        console.log('Scripts loaded for:', level);
                        this.handleConsentUpdate(level);
                    }
                }
            });
        }
    }

    private watchRouteChanges(): void {
        this.router.events.pipe(
            filter(event => event instanceof NavigationEnd)
        ).subscribe(() => {
            // Check consent state on route change
            this.checkConsentState();
        });
    }

    private checkConsentState(): void {
        const consentCookie = this.getCookie('cookie_consent_level');
        if (!consentCookie) {
            console.log('No consent given yet');
        }
    }

    private handleConsentUpdate(level: string): void {
        // Handle consent updates
        if (level === 'tracking') {
            this.initializeAnalytics();
        }
    }

    private initializeAnalytics(): void {
        // Re-initialize analytics on consent
        if ((window as any).gtag) {
            (window as any).gtag('config', 'G-XXXXXXXXXX', {
                page_path: this.router.url
            });
        }
    }

    private getCookie(name: string): string | null {
        const value = ; ${document.cookie};
        const parts = value.split(; ${name}=);
        if (parts.length === 2) {
            return parts.pop()?.split(';').shift() || null;
        }
        return null;
    }

    openPreferences(): void {
        if (window.cookieconsent && typeof window.cookieconsent.openPreferencesCenter === 'function') {
            window.cookieconsent.openPreferencesCenter();
        }
    }
}

Étape 2 : Initialisation dans le composant App

// app.component.ts
import { Component, OnInit } from '@angular/core';
import { CookieConsentService } from './services/cookie-consent.service';

@Component({
    selector: 'app-root',
    template: 
        <router-outlet></router-outlet>

        <button id="changePreferences" class="cookie-settings-btn">
            Cookie Settings
        </button>

        <noscript>
            Free cookie consent management tool by
            <a href="https://www.termsfeed.com/">TermsFeed</a>
        </noscript>
    ,
    styles: [
        .cookie-settings-btn {
            position: fixed;
            bottom: 20px;
            right: 20px;
            z-index: 9999;
            padding: 10px 20px;
            background: #007bff;
            color: white;
            border: none;
            border-radius: 5px;
            cursor: pointer;
        }
    ]
})
export class AppComponent implements OnInit {
    constructor(private cookieConsentService: CookieConsentService) {}

    ngOnInit(): void {
        this.cookieConsentService.initialize().catch(error => {
            console.error('Failed to initialize cookie consent:', error);
        });
    }
}

Problèmes courants liés aux SPA et au consentement aux cookies

Problème 1 : la bannière ne réapparaît pas lors de la navigation

Problème

L'utilisateur navigue sans faire de choix, la bannière disparaît.

Solution

Vérifiez si le consentement a été donné lors du changement de route.

// Check if consent was given on route change
router.afterEach(() => {
    const hasConsent = getCookie('cookie_consent_level');
    if (!hasConsent) {
        // Banner should be visible - it will show automatically
        // No manual action needed if properly configured
    }
});

Problème 2 : les scripts ne se chargent pas après le consentement sur de nouvelles routes

Problème

« L'utilisateur accepte les cookies, navigue vers une autre page de mon application React, mais Google Analytics ne suit pas la nouvelle page vue. »

Solution

Utilisez des callbacks pour suivre la consultation de la page à chaque changement de route.

// In your callback
"callbacks": {
    "scripts_specific_loaded": (level) => {
        if (level === 'tracking' && window.gtag) {
            // Track page view on every route change
            router.afterEach((to) => {
                gtag('config', 'G-XXXXXXXXXX', {
                    'page_path': to.path
                });
            });
        }
    }
}

Problème n° 3 : plusieurs instances de la bannière s'affichent

Problème

La bannière s'affiche plusieurs fois lors d'une navigation rapide.

Solution

Utilisez un garde pour empêcher les initialisations multiples

// Use a guard to prevent multiple initializations
let isInitialized = false;

const initCookieConsent = () => {
    if (isInitialized) return;
    isInitialized = true;

    cookieconsent.run({ / config / });
};

Ouvrir le Centre de préférences par programmation

Pour les SPA, vous pouvez ouvrir les préférences sans bouton statique :

// Method 1: Using the openPreferencesCenter() method
const openPreferences = () => {
    if (window.cookieconsent && typeof window.cookieconsent.openPreferencesCenter === 'function') {
        window.cookieconsent.openPreferencesCenter();
    } else {
        console.error('Cookie consent not initialized');
    }
};

// In React
<button onClick={openPreferences}>Change Cookie Settings</button>

// In Vue
<button @click="openPreferences">Change Cookie Settings</button>

// In Angular
<button (click)="openPreferences()">Change Cookie Settings</button>