GDPR Art. 25 (privacy by design and default) supports honoring user privacy preferences expressed through browser controls, and California's SB-27 explicitly requires certain businesses to respect the Do Not Track signal. More broadly, a user who has configured their browser to send DNT: 1 has expressed a clear privacy preference — loading analytics scripts regardless of that signal is a direct contradiction of the privacy-by-design principle. While DNT is not universally legally mandated, its absence in jurisdictions that do require it (some US states) creates a patchwork compliance gap. The implementation cost is one conditional check; the omission signals that user privacy preferences are not honored when they conflict with analytics objectives.
Info because DNT compliance is not universally legally mandated and requires no data breach to trigger, but in jurisdictions where it is required it is a standalone violation addressable with a single code change.
Check navigator.doNotTrack before initializing any non-essential analytics or tracking script. Wrap this in a helper in lib/analytics.ts so the check is applied consistently.
// lib/analytics.ts
function isDNTEnabled(): boolean {
if (typeof navigator === 'undefined') return false
return (
navigator.doNotTrack === '1' ||
(navigator as Navigator & { msDoNotTrack?: string }).msDoNotTrack === '1'
)
}
export function initAnalytics() {
if (isDNTEnabled()) return // respect user preference
// Only initialize after consent AND DNT check pass
const consent = JSON.parse(localStorage.getItem('cookie_consent') ?? '{}')
if (!consent.analytics) return
// GA4 / Segment / Amplitude initialization here
gtag('consent', 'update', { analytics_storage: 'granted' })
}
Call initAnalytics() from your consent banner's save handler rather than unconditionally on page load. This satisfies both the consent gate and the DNT signal check with a single initialization path.
ID: data-protection.compliance-documentation.dnt-signal-respected
Severity: info
What to look for: Enumerate every relevant item. Check whether the application reads the Do Not Track HTTP header or the JavaScript navigator.doNotTrack property before loading analytics and tracking scripts. The DNT header is sent by browsers when users enable "Do Not Track" in settings. While DNT is no longer legally mandated in most jurisdictions (W3C standardization stalled), respecting it is a privacy best practice and is required in some US state laws (e.g., California's SB-27). Look for code that checks navigator.doNotTrack === '1' or server-side checks for the DNT: 1 request header before initializing analytics.
Pass criteria: The application checks the DNT signal (either via navigator.doNotTrack client-side or the DNT header server-side) and disables non-essential analytics and tracking when it is set to 1.
Fail criteria: Application loads analytics and tracking regardless of the user's DNT preference. No DNT check found in analytics initialization code.
Skip (N/A) when: Application has no analytics or non-essential tracking.
Detail on fail: Example: "No DNT signal handling found in codebase. Analytics (GA4, Hotjar) loads unconditionally regardless of user's DNT browser preference.".
Remediation: Check the DNT signal before loading analytics:
// lib/analytics.ts — wrap analytics initialization
function isDNTEnabled(): boolean {
// Check browser DNT setting
if (typeof navigator !== 'undefined') {
return navigator.doNotTrack === '1' ||
(navigator as Navigator & { msDoNotTrack?: string }).msDoNotTrack === '1'
}
return false
}
export function initAnalytics() {
if (isDNTEnabled()) {
console.debug('[analytics] DNT enabled — analytics not loaded')
return
}
// Load GA4, Segment, etc. only if DNT is not set
const script = document.createElement('script')
script.src = `https://www.googletagmanager.com/gtag/js?id=${GA_ID}`
script.async = true
document.head.appendChild(script)
}
Note: DNT is a user preference signal, not a legal requirement in most jurisdictions. It does not replace your consent banner under GDPR. The two mechanisms complement each other: the consent banner handles GDPR opt-in; DNT handles additional user signals.