SanityNextJS3 MIN READ
30.12.2025
.md

Building a GDPR-Compliant Cookie Consent System with Sanity CMS

Last updated: 30.12.2025

🎧

Building a GDPR-Compliant Cookie Consent System with Sanity CMS

Audio Narration

Cookies used as a metaphore

When I started thinking about adding analytics to my blog, I quickly realized that most cookie consent solutions felt like overkill. Third-party libraries added unnecessary weight, came with their own tracking scripts, and often lacked the flexibility I wanted. So I decided to build my own—fully GDPR-compliant, privacy-first, and powered by Sanity CMS.

Why Build Your Own?

The problem with most cookie consent libraries is that they're designed for complex enterprise scenarios. For a personal blog that only uses privacy-respecting analytics (Vercel Analytics in my case), I needed something simpler:

  • No third-party scripts - Why add another tracker to manage trackers?
  • Full control - I wanted to understand exactly what was happening
  • Content management - Legal text should be editable without code changes
  • Privacy by default - No cookies until explicit consent

The Architecture

My implementation has three main components:

1. Sanity CMS as the Single Source of Truth

I created a cookieConsent singleton schema in Sanity that stores:

  • Banner text (what users see)
  • Accept/Reject button labels
  • Full cookie policy content (as Portable Text)
  • Last updated timestamp

This means my content team (okay, just me) can update the consent text, policy details, or button labels directly in the Sanity Studio—no code deployment needed.

2. React Context for State Management

A simple CookieConsentProvider handles:

  • Reading consent state from localStorage
  • Providing accept/reject/withdraw methods
  • Preventing hydration mismatches with an isMounted flag

The key insight here was to initialize state as null and read from localStorage in useEffect to avoid the flash-of-content issue during SSR.

3. Conditional Analytics Loading

The ConditionalAnalytics component only renders Vercel Analytics and SpeedInsights when consent === 'accepted'. If consent is rejected or not given, nothing loads. Simple as that.

Key Implementation Details

Accessibility First

The cookie banner includes proper ARIA attributes:

  • role="dialog" and aria-modal="true" for screen readers
  • aria-label attributes on all buttons
  • Semantic HTML throughout

Server-Side Data Fetching

Cookie consent sanity data is fetched server-side in the Next.js layout and passed as props to the banner component. This keeps the banner itself lightweight and ensures no client-side Sanity queries.

Smart Reload Logic

When transitioning from "accepted" to "rejected," I reload the page to clear any loaded analytics scripts. But rejecting when you haven't accepted yet? No reload needed—nothing to clear.

No Flash on Load

The banner checks isMounted before rendering, ensuring it only appears client-side after localStorage is read. This eliminates the annoying flash where the banner appears briefly even when consent was already given.

Learn More Link

The banner includes a "Learn more" link to the full cookie policy page, rendered from Sanity's Portable Text. Users can make an informed decision without feeling pressured.

Footer Management

A "Manage Cookie Preferences" button in the footer lets users withdraw consent at any time—a GDPR requirement that's often overlooked.

Why This Approach Works

Full Control: I understand every line of code. No black boxes.

Privacy-Focused: No analytics load until the user explicitly accepts. No sneaky preconnections or preloads.

Editor-Friendly: Non-technical users can update the policy text, banner wording, or button labels through Sanity Studio.

Performance: No third-party library to download. The entire implementation is under 5KB.

The Result

A fully GDPR-compliant cookie consent system that:

  • ✅ Respects user privacy by default
  • ✅ Loads no third-party tracking libraries
  • ✅ Provides full transparency with a detailed policy page
  • ✅ Allows users to withdraw consent anytime
  • ✅ Is fully accessible
  • ✅ Can be updated by content editors without code changes

Building your own cookie consent isn't complicated—it just requires thinking through the user experience and legal requirements. For a personal blog or small site, a custom solution often makes more sense than pulling in a heavy library.

Continue Reading
funny guy presenting
AISanity2 MIN

How I Used My Own Blog to Research My Presentation About It

The author needed a fast way to collect 14 blog posts for a presentation without manually copy-pasting from the browser. Fortunately, the blog already exposed each post as clean markdown at /md/posts/[slug], originally built for AI tools. This endpoint strips away HTML, navigation, and cookie banners, returning only the core content, which the author then fed into Claude for rapid reading and summarisation. Before working with individual posts, the author used /llms.txt, a structured, machine-readable index of the site similar to robots.txt but designed for AI discovery. Claude used this index to understand the site’s structure and locate relevant posts. For the presentation itself, the author used Slidev, a markdown-based slide tool, allowing Claude to generate and refine slides directly in markdown. The experience highlighted a “meta” benefit: infrastructure built for AI and other developers ended up being most useful to the author, demonstrating that investing in AI-friendly content formats can pay off immediately for the site owner.

Read post
varlock
NextJSSecurity5 MIN

Securing My Blog's Secrets with Varlock and Doppler

The author describes the security risks of traditional .env files in Next.js projects: secrets stored in plain text on local machines, duplicated across environments, and easily exposed through accidental commits or misconfigured deployments. Varlock is introduced as a structured, schema-based replacement for dotenv, using a committed .env.schema file that defines variable types, requirements, and sensitivity without storing actual secrets. It integrates with Next.js as a drop-in replacement for @next/env, providing TypeScript types, startup validation, sensitivity-aware client bundling, and log redaction. Doppler is chosen as the secrets provider for its generous free tier, clean dashboard, environment configs, and especially its native Vercel sync, which keeps secrets updated without tokens or runtime API calls. Locally, Varlock’s exec() calls the Doppler CLI to fetch secrets in memory; in production, Vercel’s environment variables are used directly, with the same schema validating both paths. Benefits include zero secrets on disk, early configuration error detection, automatic sensitivity handling, and an AI-friendly schema. The author would skip Varlock’s Doppler plugin next time and rely solely on the CLI-based exec() approach despite a small startup performance cost.

Read post