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 | "@tailwindcss/vite": "^4.2.2", | 22 | "@tailwindcss/vite": "^4.2.2", |
| 23 | "astro": "^6.1.3", | 23 | "astro": "^6.1.3", |
| 24 | "effect": "catalog:", | 24 | "effect": "catalog:", |
| 25 | "posthog-js": "^1.372.5", | ||
| 25 | "tailwindcss": "^4.2.2" | 26 | "tailwindcss": "^4.2.2" |
| 26 | }, | 27 | }, |
| 27 | "devDependencies": { | 28 | "devDependencies": { |
| @@ -32,6 +32,22 @@ const { · 31 unmodified lines | |||
| 32 | <body class="bg-surface text-ink antialiased selection:bg-teal/20 selection:text-teal"> | 32 | <body class="bg-surface text-ink antialiased selection:bg-teal/20 selection:text-teal"> |
| 33 | <slot /> | 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 | <script> | 51 | <script> |
| 36 | // Scroll-triggered reveals | 52 | // Scroll-triggered reveals |
| 37 | const observer = new IntersectionObserver( | 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 | "@tailwindcss/vite": "^4.2.2", | 185 | "@tailwindcss/vite": "^4.2.2", |
| 186 | "astro": "^6.1.3", | 186 | "astro": "^6.1.3", |
| 187 | "effect": "catalog:", | 187 | "effect": "catalog:", |
| 188 | "posthog-js": "^1.372.5", | ||
| 188 | "tailwindcss": "^4.2.2", | 189 | "tailwindcss": "^4.2.2", |
| 189 | }, | 190 | }, |
| 190 | "devDependencies": { | 191 | "devDependencies": { |