Mirrors the cloud app's setup: client init in Layout.astro behind PUBLIC_POSTHOG_KEY, plus an Astro middleware that reverse-proxies /api/${PUBLIC_ANALYTICS_PATH} to us.i.posthog.com and us-assets.i.posthog.com so events flow past adblockers.
Unified · Split
| @@ -22,6 +22,7 @@ · 21 unmodified lines | ||
| 22 | 22 | "@tailwindcss/vite": "^4.2.2", |
| 23 | 23 | "astro": "^6.1.3", |
| 24 | 24 | "effect": "catalog:", |
| 25 | + "posthog-js": "^1.372.5", | |
| 25 | 26 | "tailwindcss": "^4.2.2" |
| 26 | 27 | }, |
| 27 | 28 | "devDependencies": { |
| @@ -32,6 +32,22 @@ const { · 31 unmodified lines | ||
| 32 | 32 | <body class="bg-surface text-ink antialiased selection:bg-teal/20 selection:text-teal"> |
| 33 | 33 | <slot /> |
| 34 | 34 | |
| 35 | + <script> | |
| 36 | + import posthog from 'posthog-js' | |
| 37 | + | |
| 38 | + const key = import.meta.env.PUBLIC_POSTHOG_KEY | |
| 39 | + if (key) { | |
| 40 | + const analyticsPath = (import.meta.env.PUBLIC_ANALYTICS_PATH ?? 'a').replace(/^\/+|\/+$/g, '') | |
| 41 | + posthog.init(key, { | |
| 42 | + api_host: | |
| 43 | + import.meta.env.PUBLIC_POSTHOG_HOST ?? `${window.location.origin}/api/${analyticsPath}`, | |
| 44 | + ui_host: 'https://us.posthog.com', | |
| 45 | + defaults: '2025-05-24', | |
| 46 | + person_profiles: 'identified_only', | |
| 47 | + }) | |
| 48 | + } | |
| 49 | + </script> | |
| 50 | + | |
| 35 | 51 | <script> |
| 36 | 52 | // Scroll-triggered reveals |
| 37 | 53 | const observer = new IntersectionObserver( |
| @@ -0,0 +1,32 @@ | ||
| 1 | +import { defineMiddleware } from "astro:middleware"; | |
| 2 | + | |
| 3 | +// PostHog reverse proxy — the browser SDK targets a build-randomized | |
| 4 | +// first-party path and we forward to PostHog's ingest + asset hosts. Keeps | |
| 5 | +// events flowing past adblockers that match *.posthog.com. See | |
| 6 | +// https://posthog.com/docs/advanced/proxy/cloudflare | |
| 7 | + | |
| 8 | +const POSTHOG_INGEST_HOST = "us.i.posthog.com"; | |
| 9 | +const POSTHOG_ASSETS_HOST = "us-assets.i.posthog.com"; | |
| 10 | +const POSTHOG_PROXY_PATH = `/api/${(import.meta.env.PUBLIC_ANALYTICS_PATH ?? "a").replace( | |
| 11 | + /^\/+|\/+$/g, | |
| 12 | + "", | |
| 13 | +)}`; | |
| 14 | + | |
| 15 | +export const onRequest = defineMiddleware(async (context, next) => { | |
| 16 | + const { pathname } = new URL(context.request.url); | |
| 17 | + if (pathname !== POSTHOG_PROXY_PATH && !pathname.startsWith(`${POSTHOG_PROXY_PATH}/`)) { | |
| 18 | + return next(); | |
| 19 | + } | |
| 20 | + | |
| 21 | + const url = new URL(context.request.url); | |
| 22 | + url.hostname = pathname.startsWith(`${POSTHOG_PROXY_PATH}/static/`) | |
| 23 | + ? POSTHOG_ASSETS_HOST | |
| 24 | + : POSTHOG_INGEST_HOST; | |
| 25 | + url.protocol = "https:"; | |
| 26 | + url.port = ""; | |
| 27 | + url.pathname = pathname.slice(POSTHOG_PROXY_PATH.length) || "/"; | |
| 28 | + | |
| 29 | + const upstream = new Request(url, context.request); | |
| 30 | + upstream.headers.delete("cookie"); | |
| 31 | + return fetch(upstream); | |
| 32 | +}); | |
| @@ -185,6 +185,7 @@ · 184 unmodified lines | ||
| 185 | 185 | "@tailwindcss/vite": "^4.2.2", |
| 186 | 186 | "astro": "^6.1.3", |
| 187 | 187 | "effect": "catalog:", |
| 188 | + "posthog-js": "^1.372.5", | |
| 188 | 189 | "tailwindcss": "^4.2.2", |
| 189 | 190 | }, |
| 190 | 191 | "devDependencies": { |