Framework-Integrationen
While the basic script tag integration works universally, modern frameworks benefit from deeper integration using composables, hooks, and services. This guide provides ready-to-use examples for popular frameworks.
Vue 3 (Composition API)
Abschnitt betitelt „Vue 3 (Composition API)“Composable: useCMP.ts
Abschnitt betitelt „Composable: useCMP.ts“import { ref, onMounted, onUnmounted, readonly } from 'vue'
interface ConsentCategories { necessary: boolean analytics: boolean marketing: boolean preferences: boolean}
let loadPromise: Promise<void> | null = null
function loadScript(src: string, siteKey: string): Promise<void> { if (loadPromise) return loadPromise loadPromise = new Promise((resolve, reject) => { if (document.querySelector(`script[data-site="${siteKey}"]`)) { resolve(); return } const el = document.createElement('script') el.src = src el.async = true el.setAttribute('data-site', siteKey) el.onload = () => resolve() el.onerror = () => reject(new Error(`CMP script failed: ${src}`)) document.head.appendChild(el) }) return loadPromise}
export function useCMP(options?: { cmpUrl?: string; siteKey?: string }) { const isReady = ref(false) const consent = ref<{ decision: string; categories: ConsentCategories } | null>(null) let cleanups: Array<() => void> = []
const sync = () => { consent.value = window.CMP?.getConsent() ?? null }
onMounted(async () => { const url = options?.cmpUrl ?? import.meta.env.VITE_CMP_URL const key = options?.siteKey ?? import.meta.env.VITE_CMP_SITE_KEY if (!url || !key) return
await loadScript(url, key) window.CMP?.ready(() => { isReady.value = true; sync() })
const onChange = () => sync() window.CMP?.on('consent:accepted', onChange) window.CMP?.on('consent:rejected', onChange) window.CMP?.on('consent:updated', onChange) cleanups = [ () => window.CMP?.off('consent:accepted', onChange), () => window.CMP?.off('consent:rejected', onChange), () => window.CMP?.off('consent:updated', onChange), ] })
onUnmounted(() => { cleanups.forEach(fn => fn()); cleanups = [] })
return { isReady: readonly(isReady), consent: readonly(consent), show: () => window.CMP?.show(), hide: () => window.CMP?.hide(), accept: (cats?: Partial<ConsentCategories>) => window.CMP?.accept(cats), reject: () => window.CMP?.reject(), hasConsent: (cat: string) => window.CMP?.hasConsent(cat) ?? false, setLanguage: (lang: string) => window.CMP?.setLanguage(lang), }}Verwendung in App.vue
Abschnitt betitelt „Verwendung in App.vue“<script setup lang="ts">import { useCMP } from '@/composables/useCMP'import { useI18n } from 'vue-i18n'import { watch } from 'vue'
const { isReady, consent, setLanguage } = useCMP()const { locale } = useI18n()
// Sync language changes to CMPwatch(locale, (lang) => { if (isReady.value) setLanguage(lang)})</script>SSR / SSG-Optimierung (Vite SSG / Nuxt)
Abschnitt betitelt „SSR / SSG-Optimierung (Vite SSG / Nuxt)“Wenn Sie serverseitiges Rendering (SSR) oder Static Site Generation (SSG) verwenden, kann das ausschließliche clientseitige Laden des CMP-Skripts zu einer leichten Verzögerung führen. Um sicherzustellen, dass das Skript im generierten HTML enthalten ist, verwenden Sie @unhead/vue:
<script setup lang="ts">import { useCMP } from '@/composables/useCMP'import { useHead } from '@unhead/vue' // Veya Nuxt için doğrudan useHead()
const { isReady, setLanguage } = useCMP()
if (import.meta.env.VITE_CMP_URL && import.meta.env.VITE_CMP_SITE_KEY) { useHead({ script: [ { src: import.meta.env.VITE_CMP_URL, async: true, 'data-site': import.meta.env.VITE_CMP_SITE_KEY } ] })}
// ... sync logic</script>Verwendung in einer Komponente
Abschnitt betitelt „Verwendung in einer Komponente“<script setup lang="ts">import { useCMP } from '@/composables/useCMP'
const { consent, hasConsent, show } = useCMP()</script>
<template> <div v-if="consent"> <p>Analytics: {{ hasConsent('analytics') ? '✅' : '❌' }}</p> </div> <button @click="show()">Cookie Preferences</button></template>Umgebungsvariablen
Abschnitt betitelt „Umgebungsvariablen“VITE_CMP_URL=https://tool.hashentry.com/cmp.jsVITE_CMP_SITE_KEY=he_live_xxxxxxxxxxxxxReact (Hooks)
Abschnitt betitelt „React (Hooks)“Hook: useCMP.ts
Abschnitt betitelt „Hook: useCMP.ts“import { useState, useEffect, useCallback, useRef } from 'react'
interface ConsentCategories { necessary: boolean analytics: boolean marketing: boolean preferences: boolean}
let loadPromise: Promise<void> | null = null
function loadScript(src: string, siteKey: string): Promise<void> { if (loadPromise) return loadPromise loadPromise = new Promise((resolve, reject) => { if (document.querySelector(`script[data-site="${siteKey}"]`)) { resolve(); return } const el = document.createElement('script') el.src = src el.async = true el.setAttribute('data-site', siteKey) el.onload = () => resolve() el.onerror = () => reject(new Error(`CMP script failed: ${src}`)) document.head.appendChild(el) }) return loadPromise}
export function useCMP(options?: { cmpUrl?: string; siteKey?: string }) { const [isReady, setIsReady] = useState(false) const [consent, setConsent] = useState<{ decision: string; categories: ConsentCategories } | null>(null) const cleanups = useRef<Array<() => void>>([])
const sync = useCallback(() => { setConsent(window.CMP?.getConsent() ?? null) }, [])
useEffect(() => { const url = options?.cmpUrl ?? import.meta.env.VITE_CMP_URL const key = options?.siteKey ?? import.meta.env.VITE_CMP_SITE_KEY if (!url || !key) return
loadScript(url, key).then(() => { window.CMP?.ready(() => { setIsReady(true); sync() }) const onChange = () => sync() window.CMP?.on('consent:accepted', onChange) window.CMP?.on('consent:rejected', onChange) window.CMP?.on('consent:updated', onChange) cleanups.current = [ () => window.CMP?.off('consent:accepted', onChange), () => window.CMP?.off('consent:rejected', onChange), () => window.CMP?.off('consent:updated', onChange), ] })
return () => { cleanups.current.forEach(fn => fn()); cleanups.current = [] } }, [])
return { isReady, consent, show: useCallback(() => window.CMP?.show(), []), hide: useCallback(() => window.CMP?.hide(), []), accept: useCallback((cats?: Partial<ConsentCategories>) => window.CMP?.accept(cats), []), reject: useCallback(() => window.CMP?.reject(), []), hasConsent: useCallback((cat: string) => window.CMP?.hasConsent(cat) ?? false, []), setLanguage: useCallback((lang: string) => window.CMP?.setLanguage(lang), []), }}Verwendung
Abschnitt betitelt „Verwendung“import { useCMP } from './hooks/useCMP'
function CookieBanner() { const { isReady, consent, show, hasConsent } = useCMP()
return ( <div> {isReady && consent && ( <p>Analytics: {hasConsent('analytics') ? '✅' : '❌'}</p> )} <button onClick={show}>Cookie Preferences</button> </div> )}Next.js (App Router)
Abschnitt betitelt „Next.js (App Router)“Client Component Hook
Abschnitt betitelt „Client Component Hook“Im Next.js App Router muss CMP in einer Client Component geladen werden, da es auf window und document zugreift.
'use client'// Same hook as the React version above.// Import and use it in Client Components only.Layout-Integration
Abschnitt betitelt „Layout-Integration“import { CmpProvider } from '@/components/CmpProvider'
export default function RootLayout({ children }: { children: React.ReactNode }) { return ( <html lang="en"> <body> <CmpProvider /> {children} </body> </html> )}'use client'import { useCMP } from '@/hooks/useCMP'
export function CmpProvider() { useCMP({ cmpUrl: process.env.NEXT_PUBLIC_CMP_URL!, siteKey: process.env.NEXT_PUBLIC_CMP_SITE_KEY!, }) return null}Umgebungsvariablen
Abschnitt betitelt „Umgebungsvariablen“NEXT_PUBLIC_CMP_URL=https://tool.hashentry.com/cmp.jsNEXT_PUBLIC_CMP_SITE_KEY=he_live_xxxxxxxxxxxxxPlugin: cmp.client.ts
Abschnitt betitelt „Plugin: cmp.client.ts“Verwenden Sie in Nuxt 3 ein clientseitiges Plugin, um das SDK einmal beim App-Start zu laden:
export default defineNuxtPlugin(() => { const config = useRuntimeConfig() const src = config.public.cmpUrl as string const siteKey = config.public.cmpSiteKey as string
if (!src || !siteKey) return
const script = document.createElement('script') script.src = src script.async = true script.setAttribute('data-site', siteKey) document.head.appendChild(script)})Composable: useCMP.ts
Abschnitt betitelt „Composable: useCMP.ts“export function useCMP() { const isReady = ref(false) const consent = ref<Record<string, any> | null>(null)
const sync = () => { consent.value = window.CMP?.getConsent() ?? null }
onMounted(() => { window.CMP?.ready(() => { isReady.value = true; sync() }) const onChange = () => sync() window.CMP?.on('consent:accepted', onChange) window.CMP?.on('consent:rejected', onChange) window.CMP?.on('consent:updated', onChange) })
return { isReady: readonly(isReady), consent: readonly(consent), show: () => window.CMP?.show(), hide: () => window.CMP?.hide(), accept: (cats?: Record<string, boolean>) => window.CMP?.accept(cats), reject: () => window.CMP?.reject(), hasConsent: (cat: string) => window.CMP?.hasConsent(cat) ?? false, setLanguage: (lang: string) => window.CMP?.setLanguage(lang), }}Konfiguration
Abschnitt betitelt „Konfiguration“export default defineNuxtConfig({ runtimeConfig: { public: { cmpUrl: 'https://tool.hashentry.com/cmp.js', cmpSiteKey: '', // Set via NUXT_PUBLIC_CMP_SITE_KEY env } }})Angular
Abschnitt betitelt „Angular“Service: cmp.service.ts
Abschnitt betitelt „Service: cmp.service.ts“import { Injectable, signal } from '@angular/core'
@Injectable({ providedIn: 'root' })export class CmpService { isReady = signal(false) consent = signal<Record<string, any> | null>(null)
private loaded = false
init(cmpUrl: string, siteKey: string): void { if (this.loaded) return this.loaded = true
const el = document.createElement('script') el.src = cmpUrl el.async = true el.setAttribute('data-site', siteKey) el.onload = () => { (window as any).CMP?.ready(() => { this.isReady.set(true) this.sync() }) const onChange = () => this.sync() ;(window as any).CMP?.on('consent:accepted', onChange) ;(window as any).CMP?.on('consent:rejected', onChange) ;(window as any).CMP?.on('consent:updated', onChange) } document.head.appendChild(el) }
private sync(): void { this.consent.set((window as any).CMP?.getConsent() ?? null) }
show = () => (window as any).CMP?.show() hide = () => (window as any).CMP?.hide() accept = (cats?: Record<string, boolean>) => (window as any).CMP?.accept(cats) reject = () => (window as any).CMP?.reject() hasConsent = (cat: string) => (window as any).CMP?.hasConsent(cat) ?? false setLanguage = (lang: string) => (window as any).CMP?.setLanguage(lang)}App Component
Abschnitt betitelt „App Component“import { Component, OnInit, inject } from '@angular/core'import { CmpService } from './services/cmp.service'import { environment } from '../environments/environment'
@Component({ selector: 'app-root', template: '<router-outlet />' })export class AppComponent implements OnInit { private cmp = inject(CmpService)
ngOnInit() { this.cmp.init(environment.cmpUrl, environment.cmpSiteKey) }}Verwendung in einer Komponente
Abschnitt betitelt „Verwendung in einer Komponente“import { Component, inject } from '@angular/core'import { CmpService } from '../services/cmp.service'
@Component({ selector: 'app-footer', template: ` <footer> <button (click)="cmp.show()">Cookie Preferences</button> @if (cmp.isReady()) { <span>Analytics: {{ cmp.hasConsent('analytics') ? '✅' : '❌' }}</span> } </footer> `})export class FooterComponent { cmp = inject(CmpService)}Svelte (5 / Runes)
Abschnitt betitelt „Svelte (5 / Runes)“Store: cmp.svelte.ts
Abschnitt betitelt „Store: cmp.svelte.ts“let loadPromise: Promise<void> | null = null
function loadScript(src: string, siteKey: string): Promise<void> { if (loadPromise) return loadPromise loadPromise = new Promise((resolve, reject) => { if (document.querySelector(`script[data-site="${siteKey}"]`)) { resolve(); return } const el = document.createElement('script') el.src = src; el.async = true el.setAttribute('data-site', siteKey) el.onload = () => resolve() el.onerror = () => reject() document.head.appendChild(el) }) return loadPromise}
export function createCMP(cmpUrl: string, siteKey: string) { let isReady = $state(false) let consent = $state<Record<string, any> | null>(null)
const sync = () => { consent = (window as any).CMP?.getConsent() ?? null }
loadScript(cmpUrl, siteKey).then(() => { (window as any).CMP?.ready(() => { isReady = true; sync() }) ;(window as any).CMP?.on('consent:accepted', sync) ;(window as any).CMP?.on('consent:rejected', sync) ;(window as any).CMP?.on('consent:updated', sync) })
return { get isReady() { return isReady }, get consent() { return consent }, show: () => (window as any).CMP?.show(), hide: () => (window as any).CMP?.hide(), accept: (cats?: Record<string, boolean>) => (window as any).CMP?.accept(cats), reject: () => (window as any).CMP?.reject(), hasConsent: (cat: string) => (window as any).CMP?.hasConsent(cat) ?? false, setLanguage: (lang: string) => (window as any).CMP?.setLanguage(lang), }}Verwendung
Abschnitt betitelt „Verwendung“<script lang="ts"> import { createCMP } from '$lib/cmp.svelte' import { PUBLIC_CMP_URL, PUBLIC_CMP_SITE_KEY } from '$env/static/public'
const cmp = createCMP(PUBLIC_CMP_URL, PUBLIC_CMP_SITE_KEY)</script>
<slot />
<footer> <button onclick={() => cmp.show()}>Cookie Preferences</button> {#if cmp.isReady} <span>Analytics: {cmp.hasConsent('analytics') ? '✅' : '❌'}</span> {/if}</footer>Da Astro standardmäßig statisches HTML rendert, verwenden Sie ein <script>-Tag in Ihrem Layout. Verwenden Sie für interaktive Islands eine der obigen Framework-Integrationen:
<html> <head> <script src={import.meta.env.PUBLIC_CMP_URL} data-site={import.meta.env.PUBLIC_CMP_SITE_KEY} async /> </head> <body> <slot /> </body></html>Für interaktive Komponenten verwenden Sie Vue, React oder Svelte mit Astros Client Directives:
<CookieButton client:load />TypeScript Typen
Abschnitt betitelt „TypeScript Typen“Fügen Sie für alle Frameworks diese globalen Typen hinzu:
interface ConsentCategories { necessary: boolean analytics: boolean marketing: boolean preferences: boolean}
interface ConsentState { decision: 'accept' | 'reject' | 'partial' categories: ConsentCategories timestamp: string consent_token?: string}
interface ConsentResult { success: boolean consent_token: string proof_hash: string created_at: string}
interface CMPAPI { show(): void hide(): void accept(categories?: Partial<ConsentCategories>): Promise<ConsentResult> reject(): Promise<ConsentResult> getConsent(): ConsentState | null hasConsent(category: string): boolean setLanguage(lang: string): void getLanguage(): string setMetadata(key: string, value: string): void setMetadata(obj: Record<string, string>): void setRegion(region: string): void on(event: string, callback: (...args: unknown[]) => void): void off(event: string, callback: (...args: unknown[]) => void): void ready(callback: () => void): void}
declare global { interface Window { CMP?: CMPAPI __CMP_QUEUE?: Array<[string, ...unknown[]]> }}