The Setup
The site is plain HTML, CSS and JavaScript on Cloudflare Pages. There is no framework, no consent platform and no tracking plugin. Everything that touches measurement lives in two files:
-
consent.jsruns first, in the page's<head>. It sets the consent defaults, applies a stored choice, and loads Google Tag Manager. -
main.jsruns after the page is parsed. It draws the banner and pushes the events.
consent.js sets every Google signal to denied ↓ A stored choice, if there is one, updates the signals ↓ Google Tag Manager loads and fires the GA4 tag ↓ main.js shows the banner, or pushes events as you browse ↓ GA4 stores what your consent allows
Consent Comes First
The order is what makes Consent Mode work. consent.js is loaded without defer, so it runs before anything else can, and its first job is the default state:
| Signal | Default |
|---|---|
analytics_storage |
Denied |
ad_storage |
Denied |
ad_user_data |
Denied |
ad_personalization |
Denied |
It also sets wait_for_update to 500 ms and turns on ad data redaction. If you chose before, your choice is read from local storage and applied immediately, so a returning visitor never has a page view sent with the wrong state.
This is advanced mode: Google Tag Manager always loads, and until you allow analytics, GA4 sends cookieless pings without identifiers. That keeps an aggregate picture of traffic without storing anything on your device. Basic mode, which blocks Google entirely until consent, is the stricter choice, and the right one for sites that handle sensitive data, like the dental practice I set up tracking for.
The Banner
The banner is about 60 lines of JavaScript and some CSS in the site's own design. Building it myself kept the page free of third-party scripts and let me get the details right:
- Reject is as easy as accept: both buttons sit side by side with the same weight, which is what European regulators expect.
- Two separate choices: analytics and advertising can be allowed independently under Customize.
- A real update: every choice sends a Consent Mode update and a
consent_updateevent the moment you click, not on the next page. - Withdrawal cleans up: turning analytics off deletes the
_gacookies already set, instead of leaving them behind. - Always reachable: Cookie settings in the footer reopens the banner on every page.
- A limited lifetime: the choice expires after 12 months, and the banner asks again.
The Events
Page views alone don't say whether the site does its job, which is helping people decide to work with me. So the site pushes four events to the data layer, each tied to a question I actually want answered:
| Event | When it fires | Parameters |
|---|---|---|
cta_click |
A main call-to-action button is clicked | cta_text, link_url |
email_click |
An email link is clicked | link_text |
contact_submit |
The contact form is sent successfully | None |
case_study_read |
The last section of a case study scrolls into view, once per page | case_study |
The names are generic and use the snake case GA4 expects. Google Tag Manager picks each one up with a custom event trigger and forwards it to GA4. contact_submit and email_click are the ones that count as key events, since they are the closest thing this site has to a lead.
A case study counts as read when its final section reaches the screen, not when someone scrolls 90% of an arbitrary page. That makes the number comparable between short and long case studies.
What I Leave Out
What a setup refuses to collect matters as much as what it collects.
- Nothing from the contact form:
contact_submitcarries no name, email address or message. The message goes from a Cloudflare function straight to my inbox and never touches the data layer. - No advertising tags: the site doesn't run Google Ads or any other pixel. The advertising choice exists so the four Consent Mode signals are always set correctly.
- No test traffic: Google Tag Manager only loads on
sultankautsar.com, so local previews and staging deployments never reach GA4. The consent banner and the data layer still work there, which keeps them easy to test. My own visits on the live site carrytraffic_type: internal, which a GA4 data filter removes from reports. - No third-party embeds: fonts, images and the video are served from the same domain, so no other company learns about your visit.
Security Headers
Tracking scripts are code from someone else running on your page, so the site limits what they can do with a Content Security Policy, set in Cloudflare's _headers file:
- Scripts may come only from this site and Google Tag Manager.
- Data may be sent only to this site and Google's analytics and tag domains.
- Styles, fonts and media may come only from this site.
- Framing is blocked entirely, and forms may only submit to this site.
If a tag ever tried to load a script from anywhere else, the browser would refuse it. The same file adds HSTS, X-Content-Type-Options, a strict referrer policy and a Permissions Policy that turns off the camera, microphone and location.
Check It Yourself
None of this has to be taken on trust. Open your browser's developer tools on this page:
- In the Console, type
dataLayerto see the consent default, any update, and every event pushed so far. - In the Network tab, filter for
collect. Before you choose, thegcsparameter readsG100, meaning everything is denied. After accepting all, it readsG111. - In Application, look at local storage for the
consentkey, and at cookies for_ga, which only appears once you allow analytics. - In the response headers of the page itself, read the
Content-Security-Policy.
The privacy policy covers the same setup in legal terms. If you want your own site to hold up to this kind of inspection, get in touch.