TLDR: Add cookie consent to Astro in 30 seconds. One script tag in your layout. Works with React, Vue, Svelte, Solid islands. Static sites included.
Read full summary
Astro is the simplest way to build fast websites. Cookie consent should match that simplicity. One script tag gives you GDPR compliance, Google Consent Mode v2, and works with every Astro integration.
Summary by Claude AI
Astro's Simplicity, Extended to Compliance
Astro is refreshingly simple. Write HTML. Add some scripts. Ship fast sites.
Cookie consent tools often fight this simplicity. They want npm packages, build plugins, and configuration files. They add complexity where there should be none.
We follow Astro's philosophy: add a script tag, you're done.
The 30-Second Setup
---
// src/layouts/Layout.astro
---
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<script
src="https://getcookies.co/api/v1/widget/loader.js"
data-domain-id="YOUR_DOMAIN_ID"
async
></script>
</head>
<body>
<slot />
</body>
</html>
Every page using this layout now has:
- GDPR-compliant cookie banner
- Google Consent Mode v2 (all four signals)
- Script blocking until consent
- Third-party pixel support
No npm install. No configuration file. No build step.
Environment Variables
Use Astro's environment variables for different domains:
---
// src/layouts/Layout.astro
const domainId = import.meta.env.PUBLIC_GETCOOKIES_DOMAIN_ID
---
<html>
<head>
{domainId && (
<script
src="https://getcookies.co/api/v1/widget/loader.js"
data-domain-id={domainId}
async
></script>
)}
</head>
<body>
<slot />
</body>
</html>
# .env
PUBLIC_GETCOOKIES_DOMAIN_ID=your-domain-id
Island Architecture: Perfect Compatibility
Astro's island architecture works seamlessly with GetCookies. The consent script runs globally. Your interactive islands check consent when they need to.
React Island
// src/components/Analytics.tsx
import { useEffect, useState } from 'react'
export default function Analytics() {
const [hasConsent, setHasConsent] = useState(false)
useEffect(() => {
const checkConsent = () => {
setHasConsent(window.GetCookies?.hasConsent('analytics') ?? false)
}
checkConsent()
window.addEventListener('getcookies:consent', checkConsent)
return () => window.removeEventListener('getcookies:consent', checkConsent)
}, [])
useEffect(() => {
if (hasConsent) {
// Initialize analytics
console.log('Analytics enabled')
}
}, [hasConsent])
return null
}
---
import Layout from '../layouts/Layout.astro'
import Analytics from '../components/Analytics'
---
<Layout>
<Analytics client:load />
<h1>Welcome</h1>
</Layout>
Vue Island
<!-- src/components/CookieSettings.vue -->
<script setup>
import { ref, onMounted, onUnmounted } from 'vue'
const hasAnalytics = ref(false)
const hasMarketing = ref(false)
const checkConsent = () => {
hasAnalytics.value = window.GetCookies?.hasConsent('analytics') ?? false
hasMarketing.value = window.GetCookies?.hasConsent('marketing') ?? false
}
const showPreferences = () => {
window.GetCookies?.showPreferences()
}
onMounted(() => {
checkConsent()
window.addEventListener('getcookies:consent', checkConsent)
})
onUnmounted(() => {
window.removeEventListener('getcookies:consent', checkConsent)
})
</script>
<template>
<div>
<p>Analytics: {{ hasAnalytics ? 'Enabled' : 'Disabled' }}</p>
<p>Marketing: {{ hasMarketing ? 'Enabled' : 'Disabled' }}</p>
<button @click="showPreferences">Change Settings</button>
</div>
</template>
Svelte Island
<!-- src/components/ConsentStatus.svelte -->
<script>
import { onMount } from 'svelte'
let hasAnalytics = false
let hasMarketing = false
function checkConsent() {
hasAnalytics = window.GetCookies?.hasConsent('analytics') ?? false
hasMarketing = window.GetCookies?.hasConsent('marketing') ?? false
}
function showPreferences() {
window.GetCookies?.showPreferences()
}
onMount(() => {
checkConsent()
window.addEventListener('getcookies:consent', checkConsent)
return () => window.removeEventListener('getcookies:consent', checkConsent)
})
</script>
<div>
<p>Analytics: {hasAnalytics ? 'On' : 'Off'}</p>
<button on:click={showPreferences}>Cookie Settings</button>
</div>
Conditional Script Loading
Method 1: Data Attributes
Let GetCookies manage script activation:
---
// src/layouts/Layout.astro
---
<html>
<head>
<!-- GetCookies first -->
<script
src="https://getcookies.co/api/v1/widget/loader.js"
data-domain-id="YOUR_DOMAIN_ID"
async
></script>
<!-- Analytics only after consent -->
<script
type="text/plain"
data-cookieconsent="analytics"
src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXX"
></script>
<!-- Marketing pixels only after consent -->
<script type="text/plain" data-cookieconsent="marketing">
// Meta Pixel code here
</script>
</head>
<body>
<slot />
</body>
</html>
The type="text/plain" prevents execution. GetCookies activates scripts after consent.
Method 2: Inline Script
Load scripts dynamically on consent:
<script>
window.addEventListener('getcookies:consent', (e) => {
const { analytics, marketing } = e.detail
if (analytics) {
const script = document.createElement('script')
script.src = 'https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXX'
document.head.appendChild(script)
}
})
</script>
Static Sites: Perfect Fit
Astro often generates static sites. GetCookies is ideal for this:
- No server required - GetCookies runs entirely client-side
- CDN-friendly - Works on any static host
- No build-time data - Consent state is runtime-only
Hosts That Work
- Netlify
- Vercel
- Cloudflare Pages
- GitHub Pages
- AWS S3 + CloudFront
- Any static file server
Common Patterns
Footer Cookie Link
---
// src/components/Footer.astro
---
<footer>
<nav>
<a href="/privacy">Privacy Policy</a>
<button id="cookie-settings">Cookie Settings</button>
</nav>
</footer>
<script>
document.getElementById('cookie-settings')?.addEventListener('click', () => {
window.GetCookies?.showPreferences()
})
</script>
Consent-Aware Component
---
// src/components/PersonalizedSection.astro
---
<div id="personalized-section">
<div id="generic-content">
<p>Sign in for personalized recommendations</p>
</div>
<div id="personalized-content" style="display: none;">
<!-- Personalized content here -->
</div>
</div>
<script>
function updateContent() {
const hasConsent = window.GetCookies?.hasConsent('marketing') ?? false
const generic = document.getElementById('generic-content')
const personalized = document.getElementById('personalized-content')
if (generic && personalized) {
generic.style.display = hasConsent ? 'none' : 'block'
personalized.style.display = hasConsent ? 'block' : 'none'
}
}
if (document.readyState === 'complete') {
updateContent()
} else {
window.addEventListener('load', updateContent)
}
window.addEventListener('getcookies:consent', updateContent)
</script>
TypeScript Support
Add types to your env.d.ts:
// src/env.d.ts
/// <reference types="astro/client" />
interface Window {
GetCookies?: {
hasConsent: (category: 'analytics' | 'marketing' | 'preferences') => boolean
showPreferences: () => void
acceptAll: () => void
rejectAll: () => void
}
}
View Transitions
With Astro's View Transitions, GetCookies persists across navigations:
---
// src/layouts/Layout.astro
import { ViewTransitions } from 'astro:transitions'
---
<html>
<head>
<ViewTransitions />
<script
src="https://getcookies.co/api/v1/widget/loader.js"
data-domain-id="YOUR_DOMAIN_ID"
async
></script>
</head>
<body>
<slot />
</body>
</html>
Users won't see the banner again after choosing. State persists across page transitions.
Content Collections: Consent-Aware Blog
For blogs with consent-dependent features:
---
// src/pages/blog/[slug].astro
import { getCollection } from 'astro:content'
import Layout from '../../layouts/Layout.astro'
export async function getStaticPaths() {
const posts = await getCollection('blog')
return posts.map(post => ({
params: { slug: post.slug },
props: { post }
}))
}
const { post } = Astro.props
const { Content } = await post.render()
---
<Layout>
<article>
<Content />
</article>
<!-- Comments only with consent -->
<div id="comments-section">
<p id="comments-blocked">Enable cookies to view comments</p>
<div id="comments" style="display: none;"></div>
</div>
</Layout>
<script>
function updateComments() {
const hasConsent = window.GetCookies?.hasConsent('preferences') ?? false
const blocked = document.getElementById('comments-blocked')
const comments = document.getElementById('comments')
if (blocked && comments) {
blocked.style.display = hasConsent ? 'none' : 'block'
comments.style.display = hasConsent ? 'block' : 'none'
if (hasConsent) {
// Load comments widget
}
}
}
window.addEventListener('getcookies:consent', updateComments)
window.addEventListener('load', updateComments)
</script>
Performance Impact
| Metric | Impact |
|---|---|
| Bundle size | +~1.6 KB loader (gzipped); banner ~20 KB, loaded async |
| LCP | No impact |
| FID | No impact |
| CLS | No impact |
Your Lighthouse 100s stay at 100.
Deployment
Netlify
npm run build
netlify deploy --prod
Vercel
vercel --prod
Cloudflare Pages
npm run build
npx wrangler pages deploy dist
What You Don't Have to Do
- Install any npm package
- Create any configuration file
- Set up any build plugin
- Configure GTM triggers
- Read Consent Mode documentation
- Debug SSR issues (there's no SSR to debug)
One script tag. Full compliance. The Astro way.
Ready to add cookie consent to your Astro site?