PostHog Analytics
Implement privacy-first web analytics in Jekyll using PostHog with GDPR/CCPA compliance, custom event tracking, and Do Not Track support.
PostHog Analytics
Implement privacy-first, GDPR-compliant analytics in your Jekyll site using PostHog with custom event tracking and Do Not Track support.
Overview
PostHog is an open-source product analytics platform that respects user privacy. Unlike traditional analytics (Google Analytics), PostHog offers:
- Self-hostable — full data ownership option
- Privacy-first — GDPR/CCPA compliant by design
- Do Not Track support — respects browser DNT settings
- Custom events — track any user interaction
- Session recordings — optional, with input masking
- Feature flags — A/B testing built-in
- Free tier — 1 million events/month free
In this theme the integration lives in _includes/analytics/posthog.html, which
_layouts/root.html includes near the end of the page. It reads the posthog:
block from _config.yml and only emits the loader when both
site.posthog.enabled is true and jekyll.environment == "production".
Prerequisites
- PostHog account — Sign up at posthog.com
- Project API key — Found in Project Settings
- Jekyll site in production environment
Configuration
Step 1: Configure _config.yml
Add the PostHog configuration block:
These keys mirror the block shipped in this theme’s _config.yml:
# PostHog Analytics Configuration
posthog:
enabled: true
api_key: 'phc_YOUR_API_KEY_HERE'
api_host: 'https://us.i.posthog.com' # or https://eu.i.posthog.com for EU
person_profiles: 'identified_only' # 'always' | 'identified_only' | 'never'
# Automatic tracking
autocapture: true
capture_pageview: true
capture_pageleave: true
# Privacy / cookie settings
session_recording: false
disable_cookie: false # true = cookieless tracking
respect_dnt: true
cross_subdomain_cookie: false
secure_cookie: true
persistence: 'localStorage+cookie' # 'localStorage+cookie' | 'cookie' | 'memory'
# Custom event tracking
custom_events:
track_downloads: true
track_external_links: true
track_search: true
track_scroll_depth: true
# Session-recording masking + IP options
privacy:
mask_all_text: false
mask_all_inputs: true
ip_anonymization: false
Step 2: Disable in Development
In _config_dev.yml, disable analytics for local development:
posthog:
enabled: false
Verify
Because the loader is production-only, a local jekyll serve (which runs in the
development environment) never injects PostHog — that is expected. To confirm
the gate works, build with the production environment and grep the output:
# Dev build: no PostHog loader is emitted (development environment)
docker-compose exec -T jekyll bundle exec jekyll build \
--config '_config.yml,_config_dev.yml'
grep -rl "posthog.init" _site/ || echo "No PostHog in dev build (expected)"
# Production build with PostHog enabled: the loader appears
JEKYLL_ENV=production docker-compose exec -T -e JEKYLL_ENV=production jekyll \
bundle exec jekyll build
grep -rl "posthog.init" _site/ | head
In the browser, open DevTools → Console on a production page; on success the
include logs PostHog analytics loaded successfully, and accepting the analytics
cookie logs PostHog analytics enabled via consent.
Custom Event Tracking
File Downloads
Track when users download PDFs, ZIPs, and other files:
document.addEventListener('click', function(e) {
var target = e.target.closest('a');
if (target && target.href) {
var href = target.href.toLowerCase();
var downloadExts = ['.pdf', '.zip', '.doc', '.xlsx'];
var isDownload = downloadExts.some(ext => href.includes(ext));
if (isDownload) {
posthog.capture('file_download', {
'file_url': target.href,
'file_name': target.href.split('/').pop()
});
}
}
});
External Links
Track clicks to external websites:
document.addEventListener('click', function(e) {
var target = e.target.closest('a');
if (target && target.href && target.hostname !== window.location.hostname) {
posthog.capture('external_link_click', {
'link_url': target.href,
'link_text': target.innerText
});
}
});
Scroll Depth
Track how far users scroll:
var scrollDepths = [25, 50, 75, 90];
var triggeredDepths = [];
window.addEventListener('scroll', function() {
var scrollPercent = Math.round(
(window.scrollY / (document.documentElement.scrollHeight - window.innerHeight)) * 100
);
scrollDepths.forEach(function(depth) {
if (scrollPercent >= depth && !triggeredDepths.includes(depth)) {
triggeredDepths.push(depth);
posthog.capture('scroll_depth', { 'depth_percentage': depth });
}
});
});
Privacy Compliance
GDPR/CCPA Compliance
- Cookie consent integration — The PostHog loader runs in production, then
_includes/components/cookie-consent.htmlcallsposthog.opt_in_capturing()when a visitor accepts analytics cookies andposthog.opt_out_capturing()otherwise. Consent therefore gates event capturing, not whether the library loads. - Disable cookies — Set
disable_cookie: truefor cookieless tracking - IP anonymization — Set
privacy.ip_anonymization: true(the include then passesip: falsetoposthog.init) - Session recordings — Keep
session_recording: falseunless needed - Data retention — Configure in PostHog dashboard
Do Not Track Support
The implementation respects browser DNT settings:
if (navigator.doNotTrack === '1') {
console.log('PostHog: Respecting Do Not Track setting');
// PostHog not loaded
}
Troubleshooting
Analytics Not Loading
- Check environment — Must be
production, notdevelopment - Verify API key — Ensure key is correct in
_config.yml - Check browser console — Look for PostHog errors
- Test DNT setting — Try with DNT disabled
Events Not Appearing
- Wait a few minutes — Events can be delayed
- Check PostHog dashboard — Events → Live Events
- Verify autocapture — Ensure
autocapture: true - Check custom event code — Console log to debug
High Event Volume
If exceeding free tier limits:
- Disable
autocapture(captures many events) - Reduce
track_scroll_depthgranularity - Limit
session_recordingto specific pages - Use sampling in PostHog dashboard
Comparison with Google Analytics
| Feature | PostHog | Google Analytics |
|---|---|---|
| Privacy-first | Yes | Limited |
| Self-hostable | Yes | No |
| DNT support | Yes | No |
| Session recordings | Built-in | No |
| Free tier | 1M events/mo | 10M hits/mo |
| Data ownership | Full | Google-owned |
Further Reading
This guide is part of the Zer0-Mistakes Jekyll Theme documentation.
Technical Reference
For implementation details (GDPR/CCPA configuration, event tracking architecture, integration notes):
See also
- [[Features]]
- [[Google Analytics]]
- [[Cookie Consent Management]]
- [[Analytics]]