Skip to content

fix(site): replace GA4 with PostHog cookieless analytics#894

Merged
decepulis merged 5 commits intomainfrom
dc/posthog-again
Mar 11, 2026
Merged

fix(site): replace GA4 with PostHog cookieless analytics#894
decepulis merged 5 commits intomainfrom
dc/posthog-again

Conversation

@decepulis
Copy link
Copy Markdown
Collaborator

@decepulis decepulis commented Mar 11, 2026

Summary

  • Replace GA4 with PostHog using cookieless_mode: 'always' (server-side hashing, no cookies/localStorage/sessionStorage)
  • Add Netlify reverse proxy (/ph/* → PostHog)
  • Production-only guard so analytics don't load in dev
  • Update privacy policy to reflect the change

Closes #893

Context

GA4 Consent Mode v2 (all consent denied) relies on behavioral modeling that requires some users to accept cookies via a consent banner. Since the site has no consent banner and isn't planning on one, GA has zero sample data to model from, producing wildly deflated numbers (Netlify reports ~4300 hits on a blog post while GA reports ~70).

PostHog's cookieless_mode: 'always' uses server-side hashing (hash(team_id, daily_salt, ip, ua, hostname)) with no client-side storage, giving accurate pageview counts without any consent mechanism.

Changes

File Change
site/src/components/Posthog.astro New PostHog component with cookieless mode, idle-loaded via requestIdleCallback
site/src/layouts/Base.astro Remove GA4 scripts, add production-gated PostHog component
site/netlify.toml Add two redirect rules to proxy /ph/* through to PostHog
site/src/content/pages/privacy.mdx Replace GA reference with PostHog cookieless description

Setup required

"Cookieless server hash mode" must be enabled in PostHog project settings for this to work.

Test plan

  • pnpm build:site passes
  • Built HTML contains PostHog snippet, no GA/gtag references
  • Verify PostHog does NOT load in dev mode (pnpm dev)
  • Verify PostHog loads on deployed preview
  • Confirm "Cookieless server hash mode" is enabled in PostHog project settings
  • Verify pageview events appear in PostHog dashboard after deploy

🤖 Generated with Claude Code

GA4 Consent Mode v2 (all consent denied) relies on behavioral modeling
that requires some users to accept cookies via a consent banner. Since
the site has no consent banner, GA has zero sample data to model from,
producing wildly deflated numbers (e.g., Netlify reports ~4300 hits
while GA reports ~70 for the same blog post).

PostHog's cookieless_mode: 'always' uses server-side hashing with no
cookies/localStorage/sessionStorage, giving accurate counts without
any consent mechanism.

Closes #893

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@netlify
Copy link
Copy Markdown

netlify bot commented Mar 11, 2026

Deploy Preview for vjs10-site ready!

Name Link
🔨 Latest commit 8127820
🔍 Latest deploy log https://app.netlify.com/projects/vjs10-site/deploys/69b1d870e689f20008b7bd24
😎 Deploy Preview https://deploy-preview-894--vjs10-site.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

@vercel
Copy link
Copy Markdown

vercel bot commented Mar 11, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
v10-sandbox Ready Ready Preview, Comment Mar 11, 2026 9:02pm

Request Review

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 11, 2026

📦 Bundle Size Report

🎨 @videojs/html

(no changes)

Presets (7)
Entry Size
/video (default) 21.74 kB
/video (default + hls) 152.26 kB
/video (minimal) 21.56 kB
/video (minimal + hls) 151.99 kB
/audio (default) 20.48 kB
/audio (minimal) 20.49 kB
/background 6.44 kB
Media (4)
Entry Size
/media/background-video 617 B
/media/container 1.91 kB
/media/hls-video 131.23 kB
/media/simple-hls-video 11.91 kB
Players (3)
Entry Size
/video/player 6.31 kB
/audio/player 6.31 kB
/background/player 6.30 kB
Skins (15)
Entry Type Size
/video/minimal-skin.css css 2.57 kB
/video/skin.css css 2.62 kB
/video/minimal-skin js 20.98 kB
/video/minimal-skin.tailwind js 21.16 kB
/video/skin js 21.13 kB
/video/skin.tailwind js 21.39 kB
/audio/minimal-skin.css css 2.17 kB
/audio/skin.css css 2.19 kB
/audio/minimal-skin js 19.94 kB
/audio/minimal-skin.tailwind js 19.74 kB
/audio/skin js 19.94 kB
/audio/skin.tailwind js 19.97 kB
/background/skin.css css 124 B
/background/skin js 999 B
/base.css css 205 B
UI Components (21)
Entry Size
/ui/alert-dialog 2.05 kB
/ui/alert-dialog-close 1.24 kB
/ui/alert-dialog-description 1.50 kB
/ui/alert-dialog-title 1.52 kB
/ui/buffering-indicator 1.79 kB
/ui/captions-button 1.77 kB
/ui/controls 1.80 kB
/ui/fullscreen-button 1.76 kB
/ui/mute-button 1.81 kB
/ui/pip-button 1.76 kB
/ui/play-button 1.79 kB
/ui/playback-rate-button 1.80 kB
/ui/popover 3.06 kB
/ui/poster 1.74 kB
/ui/seek-button 1.78 kB
/ui/slider 2.02 kB
/ui/thumbnail 2.07 kB
/ui/time 1.82 kB
/ui/time-slider 2.93 kB
/ui/tooltip 2.39 kB
/ui/volume-slider 2.16 kB

Sizes are marginal over the root entry point.

⚛️ @videojs/react

(no changes)

Presets (7)
Entry Size
/video (default) 16.72 kB
/video (default + hls) 147.63 kB
/video (minimal) 16.72 kB
/video (minimal + hls) 147.48 kB
/audio (default) 14.41 kB
/audio (minimal) 14.44 kB
/background 3.18 kB
Media (3)
Entry Size
/media/background-video 539 B
/media/hls-video 131.52 kB
/media/simple-hls-video 12.26 kB
Skins (14)
Entry Type Size
/video/minimal-skin.css css 2.57 kB
/video/skin.css css 2.62 kB
/video/minimal-skin js 16.62 kB
/video/minimal-skin.tailwind js 19.21 kB
/video/skin js 16.62 kB
/video/skin.tailwind js 19.30 kB
/audio/minimal-skin.css css 2.17 kB
/audio/skin.css css 2.19 kB
/audio/minimal-skin js 14.35 kB
/audio/minimal-skin.tailwind js 16.17 kB
/audio/skin js 14.31 kB
/audio/skin.tailwind js 16.39 kB
/background/skin.css css 90 B
/background/skin js 272 B
UI Components (17)
Entry Size
/ui/alert-dialog 2.74 kB
/ui/buffering-indicator 2.17 kB
/ui/captions-button 2.28 kB
/ui/controls 2.19 kB
/ui/fullscreen-button 2.26 kB
/ui/mute-button 2.28 kB
/ui/pip-button 2.25 kB
/ui/play-button 2.30 kB
/ui/playback-rate-button 2.25 kB
/ui/popover 3.17 kB
/ui/poster 2.03 kB
/ui/seek-button 2.24 kB
/ui/slider 3.10 kB
/ui/time 2.40 kB
/ui/time-slider 2.80 kB
/ui/tooltip 3.24 kB
/ui/volume-slider 2.66 kB

Sizes are marginal over the root entry point.

🧩 @videojs/core

(no changes)

Entries (5)
Entry Size
. 4.78 kB
/dom 8.05 kB
/dom/media/custom-media-element 1.76 kB
/dom/media/hls 131.14 kB
/dom/media/simple-hls 11.76 kB

🏷️ @videojs/element

(no changes)

Entries (2)
Entry Size
. 999 B
/context 936 B

📦 @videojs/store

(no changes)

Entries (3)
Entry Size
. 1.32 kB
/html 700 B
/react 360 B

🔧 @videojs/utils

(no changes)

Entries (10)
Entry Size
/array 104 B
/dom 1003 B
/events 227 B
/function 261 B
/object 119 B
/predicate 265 B
/string 148 B
/style 190 B
/time 478 B
/number 158 B

📦 @videojs/spf

(no changes)

Entries (3)
Entry Size
. 40 B
/dom 10.03 kB
/playback-engine 9.93 kB

ℹ️ How to interpret

All sizes are standalone totals (minified + brotli).

Icon Meaning
No change
🔺 Increased ≤ 10%
🔴 Increased > 10%
🔽 Decreased
🆕 New (no baseline)

Run pnpm size locally to check current sizes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Without the host header, PostHog receives the Netlify domain as the
Host header and rejects the request with a 400.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The trailing-slash edge function was intercepting `/ph/e/` with a 301
redirect before the Netlify proxy could handle it, causing a 400 from
PostHog on the redirected request.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
PostHog's /privacy is their corporate policy, not a data-handling
guide for customers' end-users. Remove the link rather than point
to an irrelevant page — legal can add one back if needed.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@decepulis decepulis merged commit 0de0c28 into main Mar 11, 2026
18 of 19 checks passed
@decepulis decepulis deleted the dc/posthog-again branch March 11, 2026 21:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Replace GA4 with PostHog cookieless analytics

1 participant