A/B-testning av banners för samtycke till cookies

Utmaningen

Du vill testa olika utformningar eller budskap för cookie-banners för att förbättra samtyckesgraden samtidigt som du uppfyller kraven i GDPR.

Scenariot

”Jag vill A/B-testa vårt cookie-banner för att se om olika formuleringar påverkar acceptansgraden. Men jag är orolig för GDPR-efterlevnaden om jag spårar användare innan de har gett sitt samtycke.”

En GDPR-kompatibel metod för A/B-testning

Huvudprincipen: Du kan testa cookie-banners UTAN spårningsverktyg, genom att endast använda strikt nödvändiga cookies.

Implementering

// Create variant assignment (strictly necessary cookie)
function assignVariant() {
    let variant = getCookie('banner_test_variant');

    if (!variant) {
        // Randomly assign variant
        variant = Math.random() < 0.5 ? 'A' : 'B';

        // Store in strictly necessary cookie (doesn't require consent)
        document.cookie = banner_test_variant=${variant}; path=/; max-age=2592000; SameSite=Strict;
    }

    return variant;
}

// Configuration for Variant A (Control)
const configA = {
    "notice_banner_type": "simple",
    "website_name": "My Website",
    // ... other config
};

// Configuration for Variant B (Test)
const configB = {
    "notice_banner_type": "headline",
    "website_name": "My Site", // Shorter name
    // ... other config
};

// Initialize based on variant
document.addEventListener('DOMContentLoaded', function () {
    const variant = assignVariant();
    const config = variant === 'A' ? configA : configB;

    // Add callback to track results (only after consent)
    config.callbacks = {
        "i_agree_button_clicked": () => {
            // User accepted - log this (now they've consented to tracking)
            logConsentEvent(variant, 'accepted');
        },
        "i_decline_button_clicked": () => {
            // User declined - still log the action
            logConsentEvent(variant, 'declined');
        }
    };

    cookieconsent.run(config);
});

function logConsentEvent(variant, action) {
    // Send to your analytics (user has now consented)
    if (typeof gtag !== 'undefined') {
        gtag('event', 'cookie_consent', {
            'variant': variant,
            'action': action
        });
    }

    // Or send to backend
    fetch('/api/log-consent', {
        method: 'POST',
        headers: {'Content-Type': 'application/json'},
        body: JSON.stringify({
            variant: variant,
            action: action,
            timestamp: new Date().toISOString()
        })
    });
}

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

Testvariabler

Bannertyp

// Variant A: Simple banner
"notice_banner_type": "simple"

// Variant B: Headline banner
"notice_banner_type": "headline"

Färgschema

// Variant A: Light
"palette": "light"

// Variant B: Dark
"palette": "dark"

Knapptext (via anpassad CSS)

// Add CSS to change button text
const style = document.createElement('style');
style.textContent = variant === 'A'
    ? '.cc-nb-okagree::before { content: "Accept All"; }'
    : '.cc-nb-okagree::before { content: "I Agree"; }';
document.head.appendChild(style);

Mätning av visningar av samtyckesmeddelanden

// Backend endpoint to analyze results
// GET /api/consent-analytics

{
    "variant_A": {
        "impressions": 1000,
        "accepted": 650,
        "declined": 250,
        "no_action": 100,
        "acceptance_rate": 0.65
    },
    "variant_B": {
        "impressions": 1000,
        "accepted": 720,
        "declined": 200,
        "no_action": 80,
        "acceptance_rate": 0.72
    }
}

Domänöverskridande samtycke

Problem

"Vi har www.example.com för vår huvudsakliga webbplats och shop.example.com för vår butik. Användarna får se cookie-bannern på BÅDA webbplatserna även efter att de har godkänt den på den första. Det är irriterande och vi förlorar konverteringar."

Förstå utmaningar med flera domäner

Som standard är cookies begränsade till den specifika domän där de sätts:

  • Cookie inställd på www.example.com → Inte tillgänglig på shop.example.com
  • Cookie inställd på shop.example.com → Ej tillgänglig på www.example.com

Använd parametern cookie_domain för att dela samtycke mellan underdomäner:

cookieconsent.run({
    "consent_type": "express",
    "website_name": "My Website",
    "website_privacy_policy_url": "https://www.example.com/privacy",
    "cookie_domain": ".example.com", // Note the leading dot!
    "page_load_consent_levels": ["strictly-necessary"]
});

Viktigt! Den inledande punkten (.example.com) gör cookien tillgänglig för:

  • example.com
  • www.example.com
  • shop.example.com
  • blog.example.com
  • Alla underdomäner till example.com

Ett komplett exempel på flera domäner

Använd identisk konfiguration på alla domäner/underdomäner:

<!-- On www.example.com -->
<script type="text/javascript" src="//www.termsfeed.com/public/cookie-consent/4.2.0/cookie-consent.js"></script>
<script type="text/javascript">
document.addEventListener('DOMContentLoaded', function () {
    cookieconsent.run({
        "notice_banner_type": "headline",
        "consent_type": "express",
        "palette": "light",
        "language": "en",
        "website_name": "Example",
        "website_privacy_policy_url": "https://www.example.com/privacy",
        "open_preferences_center_selector": "#changePreferences",
        "page_load_consent_levels": ["strictly-necessary"],
        "cookie_domain": ".example.com", // Shared across all subdomains
        "cookie_secure": true // Recommended for production
    });
});
</script>
<!-- On shop.example.com - SAME configuration -->
<script type="text/javascript" src="//www.termsfeed.com/public/cookie-consent/4.2.0/cookie-consent.js"></script>
<script type="text/javascript">
document.addEventListener('DOMContentLoaded', function () {
    cookieconsent.run({
        // EXACT SAME CONFIG as main domain
        "notice_banner_type": "headline",
        "consent_type": "express",
        "palette": "light",
        "language": "en",
        "website_name": "Example",
        "website_privacy_policy_url": "https://www.example.com/privacy",
        "open_preferences_center_selector": "#changePreferences",
        "page_load_consent_levels": ["strictly-necessary"],
        "cookie_domain": ".example.com", // Same domain!
        "cookie_secure": true
    });
});
</script>

Testa inställningarna för flera domäner

  1. Besök www.example.com
  2. Acceptera cookies
  3. Öppna DevTools → Application → Cookies
  4. Kontrollera att cookiedomänen visar .example.com (med inledande punkt)
  5. Navigera till shop.example.com
  6. Kontrollera att bannern INTE visas (samtycke har redan givits)

Vanliga problem med domäner

Problem 1: Bannern visas fortfarande på underdomäner

Problem

"Jag har ställt in cookie_domain till '.example.com', men bannern visas fortfarande på alla underdomäner."

Orsak

Webbläsaren blockerar tredjepartscookies.

Lösning

Se till att cookie_secure är sant för HTTPS-webbplatser.

// Make sure cookie_secure is true for HTTPS sites
"cookie_secure": true,

// Also verify in DevTools that cookie is actually being set
// Check: Application → Cookies → Look for cookie_consent_level
// Domain should show: .example.com (not www.example.com)

Problem 2: Olika samtycken på olika underdomäner

Problem:

Användaren ger sitt samtycke på huvuddomänen, men inställningarna skiljer sig åt på underdomänen.

Orsak:

Konfigurationerna är inte identiska mellan domänerna.

Lösning:

Använd en gemensam konfigurationsfil eller variabel.

// shared-config.js (include on all domains)
const SHARED_COOKIE_CONFIG = {
    "notice_banner_type": "headline",
    "consent_type": "express",
    "palette": "light",
    "language": "en",
    "website_name": "Example",
    "website_privacy_policy_url": "https://www.example.com/privacy",
    "open_preferences_center_selector": "#changePreferences",
    "page_load_consent_levels": ["strictly-necessary"],
    "cookie_domain": ".example.com",
    "cookie_secure": true
};

// Then on each domain:
cookieconsent.run(SHARED_COOKIE_CONFIG);

Flera domäner (olika toppdomäner)

Problem

Du har example.com OCH example.co.uk, alltså helt olika domäner.

Lösning

Cookies KAN INTE delas mellan olika toppdomäner på grund av webbläsarens säkerhet.

Du har två alternativ:

Alternativ 1: Acceptera flera banners.

  • Användaren ser en banner på varje toppdomän
  • Samtycket gäller individuellt för varje domän, samtycket delas inte
  • Detta är sannolikt den mest regelkonforma metoden

Alternativ 2: Använd en synkronisering i backend (för inloggade användare)

// On example.com - user accepts
"callbacks": {
    "user_consent_saved": () => {
        if (isUserLoggedIn()) {
            // Sync to backend
            fetch('https://api.example.com/sync-consent', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    'Authorization': 'Bearer ' + getUserToken()
                },
                body: JSON.stringify({
                    consent: getConsentState(),
                    domains: ['example.com', 'example.co.uk']
                })
            });
        }
    }
}

// On example.co.uk - check backend first
async function checkBackendConsent() {
    if (isUserLoggedIn()) {
        const response = await fetch('https://api.example.com/get-consent', {
            headers: {'Authorization': 'Bearer ' + getUserToken()}
        });
        const data = await response.json();
        if (data.hasConsent) {
            // Set cookie locally
            setCookieConsent(data.consentLevel);
        }
    }
}

Sidan laddas om efter samtycke

Problem

"När användaren har godkänt cookies laddar jag om sidan med window.location.reload() för att säkerställa att spårningsskripten laddas. Men detta gör att Google Analytics förlorar den ursprungliga hänvisningskällan och visar all trafik som 'Direkt'."

Detta händer eftersom när du laddar om sidan:

  1. Rensar webbläsaren informationen om referenskällan
  2. Den nya sidan verkar komma från samma domän
  3. Google Analytics kategoriserar trafiken som ”Direkt”
  4. Du förlorar attributionsdata

Lösning 1: Ladda inte om (rekommenderas)

Använd återanrop för att ladda skript dynamiskt istället för att ladda om:

cookieconsent.run({
    "notice_banner_type": "headline",
    "consent_type": "express",
    "palette": "light",
    "language": "en",
    "website_name": "My Site",
    "website_privacy_policy_url": "https://mysite.com/privacy",
    "page_load_consent_levels": ["strictly-necessary"],

    "callbacks": {
        "scripts_specific_loaded": (level) => {
            // Scripts are NOW loaded - no reload needed!
            console.log("Scripts loaded for: " + level);

            if (level === 'tracking') {
                // Analytics is now active
                // Send initial page view if needed
                if (typeof gtag !== 'undefined') {
                    gtag('event', 'page_view', {
                        'page_location': window.location.href,
                        'page_referrer': document.referrer
                    });
                }
            }
        }
    },
    "callbacks_force": true
});

Lösning 2: Bevara referrern vid omladdning av sidan

Om du MÅSTE ladda om, behåll referrern:

cookieconsent.run({
    // ... config ...
    "callbacks": {
        "i_agree_button_clicked": () => {
            // Store referrer and source before reload
            sessionStorage.setItem('original_referrer', document.referrer);
            sessionStorage.setItem('landing_page', window.location.href);

            // Get URL parameters (utm_source, etc.)
            const urlParams = new URLSearchParams(window.location.search);
            if (urlParams.has('utm_source')) {
                sessionStorage.setItem('utm_source', urlParams.get('utm_source'));
                sessionStorage.setItem('utm_medium', urlParams.get('utm_medium'));
                sessionStorage.setItem('utm_campaign', urlParams.get('utm_campaign'));
            }

            // Now reload
            window.location.reload();
        }
    }
});

// After reload, restore attribution
document.addEventListener('DOMContentLoaded', function() {
    const savedReferrer = sessionStorage.getItem('original_referrer');
    const utmSource = sessionStorage.getItem('utm_source');

    if (savedReferrer && typeof gtag !== 'undefined') {
        // Send page view with preserved referrer
        gtag('event', 'page_view', {
            'page_location': window.location.href,
            'page_referrer': savedReferrer
        });

        // Send campaign data if available
        if (utmSource) {
            gtag('event', 'campaign_details', {
                'campaign_source': utmSource,
                'campaign_medium': sessionStorage.getItem('utm_medium'),
                'campaign_name': sessionStorage.getItem('utm_campaign')
            });
        }

        // Clear storage
        sessionStorage.removeItem('original_referrer');
        sessionStorage.removeItem('utm_source');
        // ... clear others
    }
});

Med Consent Mode V2 behövs INGEN omladdning:

<!-- 1. Set default consent to denied -->
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('consent', 'default', {
    'ad_storage': 'denied',
    'ad_user_data': 'denied',
    'ad_personalization': 'denied',
    'analytics_storage': 'denied'
});
</script>

<!-- 2. Load Google Analytics (runs in consent mode) -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('js', new Date());
gtag('config', 'G-XXXXXXXXXX');
</script>

<!-- 3. Cookie Consent with callbacks -->
<script src="//www.termsfeed.com/public/cookie-consent/4.2.0/cookie-consent.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function () {
    cookieconsent.run({
        "consent_type": "express",
        "website_name": "My Site",
        "page_load_consent_levels": ["strictly-necessary"],
        "callbacks": {
            "scripts_specific_loaded": (level) => {
                // Update consent mode - NO RELOAD!
                switch (level) {
                    case 'tracking':
                        gtag('consent', 'update', {
                            'analytics_storage': 'granted'
                        });
                        break;
                    case 'targeting':
                        gtag('consent', 'update', {
                            'ad_storage': 'granted',
                            'ad_user_data': 'granted',
                            'ad_personalization': 'granted'
                        });
                        break;
                }
            }
        },
        "callbacks_force": true
    });
});
  </script>


Inbäddat innehåll och iFrames

Problem

"Vi har YouTube-videor inbäddade på vår webbplats. Bör vi blockera iframes tills användarna accepterar cookies? Hur implementerar vi detta med TermsFeed?"

Lösning

Lösningen är att integrera en platshållare och använda dynamisk laddning.

Steg 1: Skapa platshållare i HTML

<!-- Instead of directly embedding iframe -->
<div class="video-placeholder" data-video-id="dQw4w9WgXcQ" data-consent-type="targeting">
    <div class="placeholder-content">
        <img src="/images/video-thumbnail.jpg" alt="Video thumbnail">
        <div class="placeholder-overlay">
            <button class="accept-and-load" onclick="loadVideo(this)">
                 Accept cookies to watch this video
            </button>
            <p>This video is hosted by YouTube and requires marketing cookies.</p>
            <a href="#" onclick="openCookiePreferences(); return false;">
                Change cookie preferences
            </a>
        </div>
    </div>
</div>

<style>
.video-placeholder {
    position: relative;
    width: 100%;
    padding-bottom: 56.25%; // 16:9 aspect ratio
    background: #000;
}

.placeholder-content {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
}

.placeholder-content img {
    width: 100%;
    height: 100%;
    object-fit: cover;
}

.placeholder-overlay {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
    background: rgba(0,0,0,0.7);
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    color: white;
    text-align: center;
    padding: 20px;
}

.accept-and-load {
    background: #ff0000;
    color: white;
    border: none;
    padding: 15px 30px;
    font-size: 16px;
    border-radius: 5px;
    cursor: pointer;
    margin-bottom: 15px;
}
</style>

Steg 2: Ladda iFrame efter samtycke

// Check if user has already consented
function hasConsent(consentType) {
    const consentCookie = getCookie('cookie_consent_level');
    if (!consentCookie) return false;

    // Parse consent levels
    const levels = JSON.parse(decodeURIComponent(consentCookie));
    return levels.includes(consentType);
}

// Load video dynamically
function loadVideo(button) {
    const placeholder = button.closest('.video-placeholder');
    const videoId = placeholder.dataset.videoId;
    const consentType = placeholder.dataset.consentType;

    // Check if user has consent
    if (!hasConsent(consentType)) {
        // Request consent
        if (typeof cookieconsent !== 'undefined' && cookieconsent.openPreferencesCenter) {
            cookieconsent.openPreferencesCenter();
        } else {
            alert('Please accept marketing cookies to view this content');
        }
        return;
    }

    // Create iframe
    const iframe = document.createElement('iframe');
    iframe.src = https://www.youtube.com/embed/${videoId};
    iframe.width = '100%';
    iframe.height = '100%';
    iframe.frameBorder = '0';
    iframe.allow = 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture';
    iframe.allowFullscreen = true;

    // Replace placeholder with iframe
    placeholder.innerHTML = '';
    placeholder.appendChild(iframe);
}

// Auto-load videos if consent already given
document.addEventListener('DOMContentLoaded', function() {
    document.querySelectorAll('.video-placeholder').forEach(placeholder => {
        const consentType = placeholder.dataset.consentType;
        if (hasConsent(consentType)) {
            // Auto-load - user already consented
            const videoId = placeholder.dataset.videoId;
            const iframe = document.createElement('iframe');
            iframe.src = https://www.youtube.com/embed/${videoId};
            iframe.width = '100%';
            iframe.height = '100%';
            iframe.frameBorder = '0';
            iframe.allow = 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture';
            iframe.allowFullscreen = true;
            placeholder.innerHTML = '';
            placeholder.appendChild(iframe);
        }
    });
});

// Listen for consent updates
document.addEventListener('DOMContentLoaded', function() {
    cookieconsent.run({
        // ... config ...
        "callbacks": {
            "scripts_specific_loaded": (level) => {
                // When user accepts targeting cookies, load all pending videos
                if (level === 'targeting') {
                    document.querySelectorAll('.video-placeholder').forEach(placeholder => {
                        if (placeholder.dataset.consentType === 'targeting') {
                            loadVideo(placeholder.querySelector('.accept-and-load'));
                        }
                    });
                }
            }
        }
    });
});

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

function openCookiePreferences() {
    if (typeof cookieconsent !== 'undefined' && cookieconsent.openPreferencesCenter) {
        cookieconsent.openPreferencesCenter();
    }
}

Exempel på andra inbäddningar

Google Maps

<div class="map-placeholder" data-consent-type="functionality" data-location="New York, NY">
    <div class="placeholder-overlay">
        <p>️ Accept functional cookies to view map</p>
        <button onclick="loadMap(this)">Load Map</button>
    </div>
</div>

<script>
function loadMap(button) {
    const placeholder = button.closest('.map-placeholder');
    const location = placeholder.dataset.location;

    if (!hasConsent('functionality')) {
        cookieconsent.openPreferencesCenter();
        return;
    }

    const iframe = document.createElement('iframe');
    iframe.src = https://www.google.com/maps/embed/v1/place?key=YOUR_API_KEY&q=${encodeURIComponent(location)};
    iframe.width = '100%';
    iframe.height = '450';
    iframe.style.border = '0';
    iframe.allowFullscreen = true;
    iframe.loading = 'lazy';

    placeholder.innerHTML = '';
    placeholder.appendChild(iframe);
}
</script>

Inbäddningar från sociala medier (X, Instagram, Facebook)

<div class="social-placeholder" data-consent-type="targeting" data-platform="twitter" data-url="https://twitter.com/user/status/123">
    <div class="placeholder-overlay">
        <p> Accept marketing cookies to view this tweet</p>
        <button onclick="loadSocialEmbed(this)">Load Tweet</button>
    </div>
</div>

<script>
function loadSocialEmbed(button) {
    const placeholder = button.closest('.social-placeholder');
    const platform = placeholder.dataset.platform;
    const url = placeholder.dataset.url;

    if (!hasConsent('targeting')) {
        cookieconsent.openPreferencesCenter();
        return;
    }

    // Load platform SDK
    if (platform === 'twitter' && !window.twttr) {
        const script = document.createElement('script');
        script.src = 'https://platform.twitter.com/widgets.js';
        script.onload = () => {
            twttr.widgets.createTweet(
                url.split('/').pop(),
                placeholder,
                { theme: 'light' }
            );
        };
        document.head.appendChild(script);
    }
    // Add other platforms as needed
}
</script>