June 2026 · 5 min read
Headless Cookie Consent with Cloudflare Zaraz
Eliminate heavy client-side consent bundles. Offload cookie management to the Cloudflare edge using Zaraz for performance and privacy.

Offloading Cookie Consent to the Cloudflare Edge
Most modern websites ship massive client-side bundles just to ask users for permission to load even smaller analytics packages. This is a fundamental architecture failure that compromises performance and user experience. You pay a performance penalty in time-to-interactive for a feature that could be offloaded to the edge. By moving this orchestration logic into Cloudflare Zaraz, I stopped shipping a third-party consent platform entirely.[2]
I run my static Astro site behind Cloudflare, and I successfully moved all consent orchestration to the edge. The built-in Zaraz dashboard consent modal is disabled in my project settings. Instead, I trigger consent states manually from a lightweight custom banner. This results in zero JavaScript overhead for the consent mechanism itself. The platform handles the conditional loading of tools based on the signal, keeping my core page load fast and compliant. This approach is superior because it prevents the browser from downloading redundant logic, ensuring that the visitor only processes what is necessary for the site to function as intended.
The Edge-Native Consent Flow
The core of this approach is geo-gating. You should only bother visitors in regulated regions that require explicit consent prompts. I use Zaraz Smart Dispatch with a custom HTML tool that sets a global flag, window.__zarazConsentSignal. If a user is not in a region that mandates consent, the signal automatically permits tracking for legitimate internal operations. If the signal never fires because the user is outside a relevant jurisdiction, the banner remains hidden entirely. This saves the user from the annoyance of a banner and saves you from unnecessary event handling.[1][4]
This creates a logic-driven pipeline that handles data policy at the infrastructure layer. Google Analytics only fires once the consent status is explicitly granted via the Zaraz API. By delegating the execution logic to Cloudflare Workers, I ensure that my tracking scripts remain dormant until the runtime environment receives a valid consent object. This is more robust than relying on a client-side library to intercept network requests after they have already started. You avoid the risk of data leakage caused by race conditions or slow-loading scripts that try to call home before the user has made an informed choice about their privacy preferences.[1]
Implementing a CSS-Only Consent Banner
A standard consent banner does not need a complex framework or a bloated dependency tree. I built mine with basic HTML and CSS, anchored to the bottom of the viewport using standard position utilities. It includes two buttons for users to accept or reject tracking permissions. When clicked, these buttons execute a direct call to the Zaraz consent API. This is the simplest possible implementation that remains fully compliant with the intended purpose of the platform. It removes the cognitive load for developers who otherwise have to manage state inside complex vendor-provided modal components.[3]
By binding click handlers directly to the Zaraz API methods, I map user intent to tool execution without any middleman. This skips the overhead of the default modal internal state management entirely. The browser does not need to download extra dependency blobs just to render two buttons and update a simple configuration flag. This lean implementation improves your site speed scores and reduces the bytes transferred over the wire. It demonstrates that you can maintain rigorous privacy standards while still shipping a performant user experience that respects the user connection quality and privacy choice.
Managing API Availability and Timing
The Zaraz bootstrap process is asynchronous, which causes frequent race conditions for unprepared developers. While window.zaraz exists early in the document head, the specific consent API object only appears once the internal s.js script fully initializes in the browser. My early attempts crashed consistently because I assumed the API was available immediately upon the DOMContentLoaded event. The solution is polling. You must treat the initialization as an eventually-consistent process rather than an immediate imperative block. If you write code expecting synchronous availability, your application will throw an error when the browser fails to find the required consent object.
const pollForConsent = (attempt = 0) => {
if (window.zaraz && window.zaraz.consent) {
window.zaraz.consent.setAll(true);
} else if (attempt < 25) {
setTimeout(() => pollForConsent(attempt + 1), 200);
}
};
pollForConsent();I use a small polling script to check for window.zaraz.consent every 200 milliseconds. If the object remains undefined after five seconds, I treat it as a no-op that does nothing. This pattern is robust because it gracefully handles cases where the consent manager is blocked by browser extensions or is simply slow to load over poor network conditions. It ensures that the main application does not break even if the privacy banner fails to initialize or is suppressed by aggressive privacy filters. This defensive coding style keeps your site functional under unpredictable real-world conditions.
Persistence and Debugging Patterns
Zaraz stores consent choices in local storage under a reserved zaraz-consent key. This simplifies state management significantly for the developer. On subsequent page loads, the system automatically checks this key to re-apply the user previous decision. I do not have to write any custom logic to remember that a returning visitor has already opted-in or opted-out. The platform handles the retrieval and application of these stored preferences before the rest of the application initializes. This means your analytics tools only load after the persistence layer has confirmed the user is willing to be tracked.[1]
When things fail, avoid debugging the UI first. Most issues are configuration-related rather than logical bugs in your JavaScript code. I always check the browser network tab for the s.js script to verify it loaded. I confirm my tool settings in the Zaraz dashboard to ensure they are assigned to the correct purposes. If the network request for the script is blocked or the tool is incorrectly assigned to a consent purpose, the UI will appear broken, even if the banner logic itself is sound. Trust the Zaraz tool registry above the client-side code when investigating failures.
Operational Checklist for Edge Consent
Moving to an edge-native consent model requires disciplined configuration. Assign every analytics tool in the Zaraz dashboard to a specific consent purpose to prevent premature execution. Implement a geo-gated banner using Smart Dispatch to avoid annoying visitors who are not in regulated markets. Use a polling interval with a maximum timeout when checking for the Zaraz consent object to avoid runtime errors on slow connections. Verify your local storage persistence by clearing your site data and observing the banner reappearance. Always debug via the Zaraz dashboard tools list before attempting to refactor your banner code, as configuration is usually the root cause of unexpected behavior.
Test the consent flow across multiple network conditions. Disable JavaScript in the browser to ensure the site remains accessible. Check that the console remains free of errors when the consent API fails to load.
Sources
Related articles
Automating Astro Rebuilds via Sanity Webhooks
Link your Sanity CMS content changes directly to Cloudflare Pages deploy hooks to automate your Astro site rebuilds without manual git pushes.
Durable Execution: Why I Used Cloudflare Workflows for AI Automation
Standard serverless functions crash on long AI pipelines. Here is how I used Cloudflare Workflows to handle slow APIs, self-critique loops, and stateful retries.