Skip to content

Commit 4c11f99

Browse files
committed
feat(posthog): update PostHog integration with environment variables and consent handling
1 parent 8bd4bd5 commit 4c11f99

5 files changed

Lines changed: 36 additions & 7 deletions

File tree

.env.example

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,3 +66,11 @@ NUXT_PUBLIC_SITE_URL=http://localhost:3000
6666
# GITHUB_FEEDBACK_TOKEN=ghp_...
6767
# GitHub repo in "owner/repo" format
6868
# GITHUB_FEEDBACK_REPO=reqcore/reqcore
69+
70+
# ─── Optional: Analytics (PostHog) ──────────────────────────────────────────
71+
# Privacy-focused product analytics & feature flags powered by PostHog.
72+
# Get your project API key from https://posthog.com → Project settings.
73+
# Users must accept the consent banner before any events are captured (GDPR).
74+
# POSTHOG_PUBLIC_KEY=phc_...
75+
# EU data center (default). Use https://us.i.posthog.com for US.
76+
# POSTHOG_HOST=https://eu.i.posthog.com

Dockerfile

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,14 @@ COPY . .
1313
ARG NUXT_PUBLIC_SITE_URL=http://localhost:3000
1414
ENV NUXT_PUBLIC_SITE_URL=${NUXT_PUBLIC_SITE_URL}
1515

16+
# PostHog — the @posthog/nuxt module is conditionally loaded at build time.
17+
# Pass your project API key so the module is included in the production bundle.
18+
# Railway auto-passes service variables as Docker build args.
19+
ARG POSTHOG_PUBLIC_KEY
20+
ENV POSTHOG_PUBLIC_KEY=${POSTHOG_PUBLIC_KEY}
21+
ARG POSTHOG_HOST
22+
ENV POSTHOG_HOST=${POSTHOG_HOST}
23+
1624
RUN npm run build
1725

1826
# ─── Stage 2: Run ────────────────────────────────────────────────────────────

app/composables/useAnalyticsConsent.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ export function useAnalyticsConsent() {
1515
// usePostHog() is auto-imported by @posthog/nuxt, but the module is
1616
// conditionally loaded. Replicate the safe accessor so this composable
1717
// works even when PostHog is not configured (CI, self-hosted without key).
18-
const posthog = (useNuxtApp() as Record<string, unknown>).$posthog as ((() => { opt_in_capturing: () => void, opt_out_capturing: () => void }) | undefined)
18+
const posthog = (useNuxtApp() as Record<string, unknown>).$posthog as ((() => { opt_in_capturing: () => void, opt_out_capturing: () => void, capture: (event: string, properties?: Record<string, unknown>) => void }) | undefined)
1919
const ph = posthog?.()
2020

2121
const consentState = useState<ConsentState>('analytics-consent', () => null)
@@ -43,6 +43,16 @@ export function useAnalyticsConsent() {
4343
localStorage.setItem(CONSENT_KEY, 'granted')
4444
}
4545
ph?.opt_in_capturing()
46+
// Capture the entry-page pageview now that the user has opted in.
47+
// PostHog's init-time $pageview was suppressed by opt_out_capturing_by_default,
48+
// and subsequent pushState events only fire after this call, so we must
49+
// manually send one for the page the user is currently on.
50+
if (import.meta.client) {
51+
const url = new URL(window.location.href)
52+
url.search = ''
53+
url.hash = ''
54+
ph?.capture('$pageview', { $current_url: url.toString() })
55+
}
4656
}
4757

4858
function declineAnalytics() {

app/plugins/posthog-identity.client.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ const SENSITIVE_URL_PROPS = ['$current_url', '$initial_current_url', '$referrer'
2121

2222
export default defineNuxtPlugin({
2323
name: 'posthog-identity',
24-
parallel: true,
2524
setup() {
2625
// usePostHog() is auto-imported by @posthog/nuxt, but the module is
2726
// conditionally loaded (only when POSTHOG_PUBLIC_KEY is set). In CI and

nuxt.config.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,10 @@ export default defineNuxtConfig({
5757
publicKey: process.env.POSTHOG_PUBLIC_KEY || '',
5858
host: process.env.POSTHOG_HOST || 'https://eu.i.posthog.com',
5959
clientConfig: {
60+
// ── Reverse proxy: route PostHog through reqcore.com to bypass ad blockers ──
61+
// Requests to /ingest/** are proxied by Nitro to eu.i.posthog.com
62+
api_host: '/ingest',
63+
ui_host: 'https://eu.posthog.com',
6064
// ── Privacy: disable invasive features ──
6165
autocapture: false,
6266
disable_session_recording: true,
@@ -117,11 +121,8 @@ export default defineNuxtConfig({
117121

118122
runtimeConfig: {
119123
public: {
120-
/** PostHog public key and host for server-side event capture */
121-
posthog: {
122-
publicKey: process.env.POSTHOG_PUBLIC_KEY || '',
123-
host: process.env.POSTHOG_HOST || 'https://eu.i.posthog.com',
124-
},
124+
// PostHog runtimeConfig is managed by @posthog/nuxt via posthogConfig above.
125+
// Override at runtime with NUXT_PUBLIC_POSTHOG_PUBLIC_KEY / NUXT_PUBLIC_POSTHOG_HOST.
125126
/** When set, the dashboard shows a read-only demo banner for this org slug */
126127
demoOrgSlug: process.env.DEMO_ORG_SLUG || (isRailwayPreview ? 'reqcore-demo' : ''),
127128
/** Public live-demo account email used to prefill sign-in */
@@ -202,6 +203,9 @@ export default defineNuxtConfig({
202203
// Route rules — prerender/ISR for public pages
203204
// ─────────────────────────────────────────────
204205
routeRules: {
206+
// ── PostHog reverse proxy — bypasses ad blockers by routing through reqcore.com ──
207+
'/ingest/static/**': { proxy: 'https://eu-assets.i.posthog.com/static/**' },
208+
'/ingest/**': { proxy: 'https://eu.i.posthog.com/**' },
205209
'/': { prerender: true },
206210
'/roadmap': { prerender: true },
207211
'/blog': { prerender: true },

0 commit comments

Comments
 (0)