The DataFast script writes datafast_visitor_id, a first-party cookie that
lasts a year. It is what makes returning visitors and revenue attribution work.
Under the EU ePrivacy rules and the UK's PECR, storing that on a visitor's device is not "strictly necessary". So in those places the script cannot run until the visitor says yes. Elsewhere, often no banner is needed. That call belongs to whoever owns your privacy policy. This page is about making the code honour it.
The three places data leaves
A consent gate that only covers the script tag leaks in two other places:
- The script tag. Rendering it sets the cookie.
- Queued goals. Code that calls
window.datafast("signup_started")before the script loads pushes onto a queue. If the script loads later, the queue is sent. A goal fired before consent goes out after it. - Checkout metadata. Your server copies the visitor id into Stripe or Polar metadata. If the cookie exists from an earlier visit, the id leaves for a visitor who withdrew consent.
Gate all three on one flag.
One flag, readable on both sides
Store the answer in a first-party cookie, not localStorage. The server can read a cookie. It cannot read localStorage.
export const CONSENT_MODE: "required" | "implied" = "required";
export const CONSENT_COOKIE = "analytics_consent";
export function hasAnalyticsConsent(): boolean {
if (CONSENT_MODE === "implied") return true;
if (typeof document === "undefined") return false;
return document.cookie.split(";").some((c) => c.trim() === `${CONSENT_COOKIE}=granted`);
}
export function grantAnalyticsConsent(): void {
document.cookie = `${CONSENT_COOKIE}=granted; Max-Age=31536000; Path=/; SameSite=Lax`;
window.dispatchEvent(new CustomEvent("analytics-consent-change"));
}
Gate the render
Read consent after mount, so server and client HTML agree, and render the tag only when it is true:
"use client";
export function DataFastScript() {
const [allowed, setAllowed] = useState(false);
useEffect(() => {
setAllowed(hasAnalyticsConsent());
const update = () => setAllowed(hasAnalyticsConsent());
window.addEventListener("analytics-consent-change", update);
return () => window.removeEventListener("analytics-consent-change", update);
}, []);
if (!allowed) return null;
return <Script src="/js/script.js" data-website-id="dfid_..." strategy="afterInteractive" />;
}
When the banner calls grantAnalyticsConsent(), the state flips and
next/script loads the tag on the spot. No reload.
Gate the queue
export function trackGoal(name: string, params?: Record<string, string>): void {
if (!hasAnalyticsConsent()) return; // drop, do not queue
window.datafast?.(name, params);
}
Gate the checkout
const jar = await cookies();
if (CONSENT_MODE === "required" && jar.get(CONSENT_COOKIE)?.value !== "granted") {
return {}; // no attribution without consent
}
Withdrawal
Withdrawing must be as easy as granting. The script cannot be unloaded once it
ran, so delete the consent cookie and reload. The next page view has no script,
no queue and no attribution. The old datafast_visitor_id cookie stays until it
expires, but nothing reads it any more.
The cost, stated plainly
With consent required, you measure only people who clicked yes. Expect a large drop in visitors and some revenue with no channel. That is the correct number for your jurisdiction, not a bug to fix.
A reverse proxy does not change any of this. It hides a hostname from blocklists. It is not a legal basis.