From a273518a1537563787658b538a4b16c5bb501e63 Mon Sep 17 00:00:00 2001 From: Pranav Karthik Date: Thu, 18 Dec 2025 15:43:00 -0800 Subject: [PATCH 1/2] initial commit --- cdn/posthog-analytics-proxy/.env.example | 3 + cdn/posthog-analytics-proxy/.eslintrc.json | 7 + cdn/posthog-analytics-proxy/.gitignore | 44 + cdn/posthog-analytics-proxy/README.md | 88 ++ cdn/posthog-analytics-proxy/app/globals.css | 2 + cdn/posthog-analytics-proxy/app/layout.tsx | 34 + cdn/posthog-analytics-proxy/app/page.tsx | 71 ++ .../components/Analytics.tsx | 22 + .../components/EventTracker.tsx | 53 + .../components/Header.tsx | 23 + cdn/posthog-analytics-proxy/next.config.mjs | 10 + cdn/posthog-analytics-proxy/package.json | 32 + cdn/posthog-analytics-proxy/pnpm-lock.yaml | 1084 +++++++++++++++++ .../postcss.config.mjs | 6 + cdn/posthog-analytics-proxy/tsconfig.json | 44 + cdn/posthog-analytics-proxy/vercel.ts | 20 + 16 files changed, 1543 insertions(+) create mode 100644 cdn/posthog-analytics-proxy/.env.example create mode 100644 cdn/posthog-analytics-proxy/.eslintrc.json create mode 100644 cdn/posthog-analytics-proxy/.gitignore create mode 100644 cdn/posthog-analytics-proxy/README.md create mode 100644 cdn/posthog-analytics-proxy/app/globals.css create mode 100644 cdn/posthog-analytics-proxy/app/layout.tsx create mode 100644 cdn/posthog-analytics-proxy/app/page.tsx create mode 100644 cdn/posthog-analytics-proxy/components/Analytics.tsx create mode 100644 cdn/posthog-analytics-proxy/components/EventTracker.tsx create mode 100644 cdn/posthog-analytics-proxy/components/Header.tsx create mode 100644 cdn/posthog-analytics-proxy/next.config.mjs create mode 100644 cdn/posthog-analytics-proxy/package.json create mode 100644 cdn/posthog-analytics-proxy/pnpm-lock.yaml create mode 100644 cdn/posthog-analytics-proxy/postcss.config.mjs create mode 100644 cdn/posthog-analytics-proxy/tsconfig.json create mode 100644 cdn/posthog-analytics-proxy/vercel.ts diff --git a/cdn/posthog-analytics-proxy/.env.example b/cdn/posthog-analytics-proxy/.env.example new file mode 100644 index 0000000000..1455fc160c --- /dev/null +++ b/cdn/posthog-analytics-proxy/.env.example @@ -0,0 +1,3 @@ +# PostHog API key from https://posthog.com +# Get your key from: Project Settings → API Keys +NEXT_PUBLIC_POSTHOG_KEY=phc_your_project_key_here diff --git a/cdn/posthog-analytics-proxy/.eslintrc.json b/cdn/posthog-analytics-proxy/.eslintrc.json new file mode 100644 index 0000000000..7d16772f11 --- /dev/null +++ b/cdn/posthog-analytics-proxy/.eslintrc.json @@ -0,0 +1,7 @@ +{ + "extends": "next/core-web-vitals", + "rules": { + "@next/next/no-html-link-for-pages": "off" + } +} + diff --git a/cdn/posthog-analytics-proxy/.gitignore b/cdn/posthog-analytics-proxy/.gitignore new file mode 100644 index 0000000000..ceb7e9eb2f --- /dev/null +++ b/cdn/posthog-analytics-proxy/.gitignore @@ -0,0 +1,44 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# Dependencies +/node_modules +/.pnp +.pnp.js + +# Testing +/coverage + +# Next.js +/.next/ +/out/ +next-env.d.ts + +# Production +build +dist + +# Misc +.DS_Store +*.pem + +# Debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Local ENV files +.env.local +.env.development.local +.env.test.local +.env.production.local + +# Vercel +.vercel + +# Turborepo +.turbo + +# typescript +*.tsbuildinfo +.env*.local + diff --git a/cdn/posthog-analytics-proxy/README.md b/cdn/posthog-analytics-proxy/README.md new file mode 100644 index 0000000000..3740851c9a --- /dev/null +++ b/cdn/posthog-analytics-proxy/README.md @@ -0,0 +1,88 @@ +--- +name: PostHog analytics reverse proxy (vercel.ts) +slug: posthog-analytics-proxy +description: Proxy PostHog analytics requests through Vercel to bypass ad blockers using vercel.ts. +framework: Next.js +useCase: Analytics +css: Tailwind +deployUrl: https://vercel.com/new/clone?repository-url=https://github.com/vercel/examples/tree/main/cdn/posthog-analytics-proxy&project-name=posthog-analytics-proxy&repository-name=posthog-analytics-proxy&env=NEXT_PUBLIC_POSTHOG_KEY +demoUrl: https://posthog-analytics-proxy.vercel.app +--- + +# PostHog analytics reverse proxy (vercel.ts) example + +This example shows how to proxy PostHog analytics requests through Vercel's routing layer to bypass ad blockers. By proxying requests through your own domain instead of sending them directly to PostHog, ad blockers that filter requests to analytics domains won't catch them. + +## Demo + +https://posthog-analytics-proxy.vercel.app + +## How to Use + +You can choose from one of the following two methods to use this repository: + +### One-Click Deploy + +Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=vercel-examples): + +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https://github.com/vercel/examples/tree/main/cdn/posthog-analytics-proxy&project-name=posthog-analytics-proxy&repository-name=posthog-analytics-proxy&env=NEXT_PUBLIC_POSTHOG_KEY) + +### Clone and Deploy + +Execute [`create-next-app`](https://github.com/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example: + +```bash +pnpm create next-app --example https://github.com/vercel/examples/tree/main/cdn/posthog-analytics-proxy +``` + +Next, run Next.js in development mode: + +```bash +pnpm dev +``` + +## Environment variables + +- `NEXT_PUBLIC_POSTHOG_KEY` – Your PostHog project API key + +## How it works + +1. `vercel.ts` runs at deployment and configures two proxy routes: + - `/ph/static/(.*)` → `https://us-assets.i.posthog.com/static/$1` (static assets) + - `/ph/(.*)` → `https://us.i.posthog.com/$1` (API requests) +2. The `host` header is rewritten to the PostHog domain so their servers correctly route the requests. +3. Initialize PostHog with `api_host: '/ph'` to point all requests through your proxy. +4. Requests now appear to come from your domain, so domain-based ad blockers don't filter them out. + +## Benefits + +- **Bypass ad blockers**: Analytics requests aren't filtered by domain-based blockers +- **No configuration needed**: Just set your API key and deploy +- **Works everywhere**: Works with Next.js, Vue, Astro, or any framework +- **No edge function costs**: Uses Vercel's reverse proxy layer +- **Transparent**: PostHog receives the data normally, your app works as expected + +## PostHog initialization + +In your Next.js app, initialize PostHog with the proxy path: + +```jsx +// app/layout.tsx or pages/_app.tsx +import PostHog from 'posthog-js' + +if (typeof window !== 'undefined') { + PostHog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY, { + api_host: '/ph', + ui_host: 'https://us.posthog.com', + }) +} +``` + +Now all analytics calls go through your `/ph` proxy endpoint instead of directly to PostHog's domain. + +## Learn More + +- [PostHog Reverse Proxy Documentation](https://posthog.com/docs/advanced/proxy) +- [Vercel Reverse Proxy Documentation](https://vercel.com/kb/guide/vercel-reverse-proxy-rewrites-external) +- [Vercel Config Documentation](https://vercel.com/docs/project-configuration) + diff --git a/cdn/posthog-analytics-proxy/app/globals.css b/cdn/posthog-analytics-proxy/app/globals.css new file mode 100644 index 0000000000..3d552a61f5 --- /dev/null +++ b/cdn/posthog-analytics-proxy/app/globals.css @@ -0,0 +1,2 @@ +@import "tailwindcss"; + diff --git a/cdn/posthog-analytics-proxy/app/layout.tsx b/cdn/posthog-analytics-proxy/app/layout.tsx new file mode 100644 index 0000000000..c36cbd375f --- /dev/null +++ b/cdn/posthog-analytics-proxy/app/layout.tsx @@ -0,0 +1,34 @@ +import type { Metadata } from 'next' +import { Analytics } from '@/components/Analytics' +import { Header } from '@/components/Header' +import { EventTracker } from '@/components/EventTracker' +import './globals.css' + +export const metadata: Metadata = { + title: 'PostHog Analytics Proxy', + description: 'Proxy PostHog analytics through Vercel to bypass ad blockers', +} + +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + + + + + + +
+
+ {children} + +
+ + + ) +} + diff --git a/cdn/posthog-analytics-proxy/app/page.tsx b/cdn/posthog-analytics-proxy/app/page.tsx new file mode 100644 index 0000000000..0b9ffcae99 --- /dev/null +++ b/cdn/posthog-analytics-proxy/app/page.tsx @@ -0,0 +1,71 @@ +export default function Home() { + return ( +
+
+

PostHog Analytics Proxy

+

+ This demo shows how to proxy PostHog analytics requests through Vercel to bypass ad blockers. +

+
+
+

How it works

+
    +
  • • Requests to /ph/* are proxied to PostHog
  • +
  • • The host header is rewritten for proper routing
  • +
  • • Analytics appear to come from your domain
  • +
  • • Ad blockers don't filter domain-based requests
  • +
+
+
+

Benefits

+
    +
  • ✓ Bypass ad blockers
  • +
  • ✓ Better data collection
  • +
  • ✓ No edge function costs
  • +
  • ✓ Works with any framework
  • +
+
+
+
+ +
+

vercel.ts Configuration

+

+ The routing is configured in vercel.ts: +

+
+          {`routes: [
+  routes.rewrite('/ph/static/(.*)', 
+    'https://us-assets.i.posthog.com/static/$1',
+    { requestHeaders: { 'host': 'us-assets.i.posthog.com' } }
+  ),
+  routes.rewrite('/ph/(.*)', 
+    'https://us.i.posthog.com/$1',
+    { requestHeaders: { 'host': 'us.i.posthog.com' } }
+  ),
+]`}
+        
+
+ +
+

PostHog Initialization

+

+ Initialize PostHog with the proxy path: +

+
+          {`PostHog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY, {
+  api_host: '/ph',
+  ui_host: 'https://us.posthog.com',
+})`}
+        
+
+ +
+

+ 👉 Tip: Click the buttons below to trigger events and see them tracked through the proxy. +

+
+
+ ) +} + diff --git a/cdn/posthog-analytics-proxy/components/Analytics.tsx b/cdn/posthog-analytics-proxy/components/Analytics.tsx new file mode 100644 index 0000000000..915e0bea80 --- /dev/null +++ b/cdn/posthog-analytics-proxy/components/Analytics.tsx @@ -0,0 +1,22 @@ +'use client' + +import { useEffect } from 'react' +import posthog from 'posthog-js' + +export function Analytics() { + useEffect(() => { + if (typeof window === 'undefined') return + + posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY, { + api_host: '/ph', + ui_host: 'https://us.posthog.com', + loaded: (posthog) => { + posthog.identify() + posthog.capture('page_viewed') + }, + }) + }, []) + + return null +} + diff --git a/cdn/posthog-analytics-proxy/components/EventTracker.tsx b/cdn/posthog-analytics-proxy/components/EventTracker.tsx new file mode 100644 index 0000000000..66470521b7 --- /dev/null +++ b/cdn/posthog-analytics-proxy/components/EventTracker.tsx @@ -0,0 +1,53 @@ +'use client' + +import { useCallback } from 'react' +import posthog from 'posthog-js' + +export function EventTracker() { + const trackEvent = useCallback((eventName: string) => { + posthog.capture(eventName, { + timestamp: new Date().toISOString(), + }) + }, []) + + return ( +
+

Test Event Tracking

+

+ Click the buttons below to trigger events. Check your PostHog dashboard to see them tracked through the proxy. +

+
+ + + + +
+
+

+ Network insight: Open your browser's Network tab and filter for /ph requests. You'll see all analytics calls going through your domain instead of directly to PostHog. +

+
+
+ ) +} + diff --git a/cdn/posthog-analytics-proxy/components/Header.tsx b/cdn/posthog-analytics-proxy/components/Header.tsx new file mode 100644 index 0000000000..3161a98739 --- /dev/null +++ b/cdn/posthog-analytics-proxy/components/Header.tsx @@ -0,0 +1,23 @@ +export function Header() { + return ( +
+
+
+
+ P +
+

PostHog Proxy

+
+ + Documentation → + +
+
+ ) +} + diff --git a/cdn/posthog-analytics-proxy/next.config.mjs b/cdn/posthog-analytics-proxy/next.config.mjs new file mode 100644 index 0000000000..b68ac7a82b --- /dev/null +++ b/cdn/posthog-analytics-proxy/next.config.mjs @@ -0,0 +1,10 @@ +/* + * For a detailed explanation regarding each configuration property and type check, visit: + * https://nextjs.org/docs/advanced/config-files/typescript-config + */ + +/** @type {import('next').NextConfig} */ +const nextConfig = {} + +export default nextConfig + diff --git a/cdn/posthog-analytics-proxy/package.json b/cdn/posthog-analytics-proxy/package.json new file mode 100644 index 0000000000..b452d91cab --- /dev/null +++ b/cdn/posthog-analytics-proxy/package.json @@ -0,0 +1,32 @@ +{ + "name": "posthog-analytics-proxy", + "version": "1.0.0", + "private": true, + "repository": "https://github.com/vercel/examples.git", + "license": "MIT", + "type": "module", + "scripts": { + "dev": "next dev --turbopack", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "@vercel/config": "^0.0.22", + "geist": "^1.5.1", + "next": "^16.0.7", + "posthog-js": "^1.150.0", + "react": "^19.2.1", + "react-dom": "^19.2.1" + }, + "devDependencies": { + "@tailwindcss/postcss": "^4", + "@types/node": "^20", + "@types/react": "^19", + "@types/react-dom": "^19", + "tailwindcss": "^4", + "typescript": "^5" + }, + "packageManager": "pnpm@10.24.0+sha512.01ff8ae71b4419903b65c60fb2dc9d34cf8bb6e06d03bde112ef38f7a34d6904c424ba66bea5cdcf12890230bf39f9580473140ed9c946fef328b6e5238a345a" +} + diff --git a/cdn/posthog-analytics-proxy/pnpm-lock.yaml b/cdn/posthog-analytics-proxy/pnpm-lock.yaml new file mode 100644 index 0000000000..d2f99dec39 --- /dev/null +++ b/cdn/posthog-analytics-proxy/pnpm-lock.yaml @@ -0,0 +1,1084 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@vercel/config': + specifier: ^0.0.22 + version: 0.0.22 + geist: + specifier: ^1.5.1 + version: 1.5.1(next@16.1.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)) + next: + specifier: ^16.0.7 + version: 16.1.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + posthog-js: + specifier: ^1.150.0 + version: 1.309.1 + react: + specifier: ^19.2.1 + version: 19.2.3 + react-dom: + specifier: ^19.2.1 + version: 19.2.3(react@19.2.3) + devDependencies: + '@tailwindcss/postcss': + specifier: ^4 + version: 4.1.18 + '@types/node': + specifier: ^20 + version: 20.19.27 + '@types/react': + specifier: ^19 + version: 19.2.7 + '@types/react-dom': + specifier: ^19 + version: 19.2.3(@types/react@19.2.7) + tailwindcss: + specifier: ^4 + version: 4.1.18 + typescript: + specifier: ^5 + version: 5.9.3 + +packages: + + '@alloc/quick-lru@5.2.0': + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + + '@emnapi/runtime@1.7.1': + resolution: {integrity: sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==} + + '@img/colour@1.0.0': + resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==} + engines: {node: '>=18'} + + '@img/sharp-darwin-arm64@0.34.5': + resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.34.5': + resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.2.4': + resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.2.4': + resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.2.4': + resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linux-arm@1.2.4': + resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} + cpu: [arm] + os: [linux] + + '@img/sharp-libvips-linux-ppc64@1.2.4': + resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} + cpu: [ppc64] + os: [linux] + + '@img/sharp-libvips-linux-riscv64@1.2.4': + resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} + cpu: [riscv64] + os: [linux] + + '@img/sharp-libvips-linux-s390x@1.2.4': + resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} + cpu: [s390x] + os: [linux] + + '@img/sharp-libvips-linux-x64@1.2.4': + resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} + cpu: [x64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} + cpu: [x64] + os: [linux] + + '@img/sharp-linux-arm64@0.34.5': + resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linux-arm@0.34.5': + resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + + '@img/sharp-linux-ppc64@0.34.5': + resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ppc64] + os: [linux] + + '@img/sharp-linux-riscv64@0.34.5': + resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [riscv64] + os: [linux] + + '@img/sharp-linux-s390x@0.34.5': + resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + + '@img/sharp-linux-x64@0.34.5': + resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-linuxmusl-arm64@0.34.5': + resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linuxmusl-x64@0.34.5': + resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-wasm32@0.34.5': + resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-arm64@0.34.5': + resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-ia32@0.34.5': + resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.34.5': + resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@next/env@16.1.0': + resolution: {integrity: sha512-Dd23XQeFHmhf3KBW76leYVkejHlCdB7erakC2At2apL1N08Bm+dLYNP+nNHh0tzUXfPQcNcXiQyacw0PG4Fcpw==} + + '@next/swc-darwin-arm64@16.1.0': + resolution: {integrity: sha512-onHq8dl8KjDb8taANQdzs3XmIqQWV3fYdslkGENuvVInFQzZnuBYYOG2HGHqqtvgmEU7xWzhgndXXxnhk4Z3fQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@next/swc-darwin-x64@16.1.0': + resolution: {integrity: sha512-Am6VJTp8KhLuAH13tPrAoVIXzuComlZlMwGr++o2KDjWiKPe3VwpxYhgV6I4gKls2EnsIMggL4y7GdXyDdJcFA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@next/swc-linux-arm64-gnu@16.1.0': + resolution: {integrity: sha512-fVicfaJT6QfghNyg8JErZ+EMNQ812IS0lmKfbmC01LF1nFBcKfcs4Q75Yy8IqnsCqH/hZwGhqzj3IGVfWV6vpA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@next/swc-linux-arm64-musl@16.1.0': + resolution: {integrity: sha512-TojQnDRoX7wJWXEEwdfuJtakMDW64Q7NrxQPviUnfYJvAx5/5wcGE+1vZzQ9F17m+SdpFeeXuOr6v3jbyusYMQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@next/swc-linux-x64-gnu@16.1.0': + resolution: {integrity: sha512-quhNFVySW4QwXiZkZ34SbfzNBm27vLrxZ2HwTfFFO1BBP0OY1+pI0nbyewKeq1FriqU+LZrob/cm26lwsiAi8Q==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@next/swc-linux-x64-musl@16.1.0': + resolution: {integrity: sha512-6JW0z2FZUK5iOVhUIWqE4RblAhUj1EwhZ/MwteGb//SpFTOHydnhbp3868gxalwea+mbOLWO6xgxj9wA9wNvNw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@next/swc-win32-arm64-msvc@16.1.0': + resolution: {integrity: sha512-+DK/akkAvvXn5RdYN84IOmLkSy87SCmpofJPdB8vbLmf01BzntPBSYXnMvnEEv/Vcf3HYJwt24QZ/s6sWAwOMQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@next/swc-win32-x64-msvc@16.1.0': + resolution: {integrity: sha512-Tr0j94MphimCCks+1rtYPzQFK+faJuhHWCegU9S9gDlgyOk8Y3kPmO64UcjyzZAlligeBtYZ/2bEyrKq0d2wqQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@posthog/core@1.8.1': + resolution: {integrity: sha512-jfzBtQIk9auRi/biO+G/gumK5KxqsD5wOr7XpYMROE/I3pazjP4zIziinp21iQuIQJMXrDvwt9Af3njgOGwtew==} + + '@swc/helpers@0.5.15': + resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} + + '@tailwindcss/node@4.1.18': + resolution: {integrity: sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==} + + '@tailwindcss/oxide-android-arm64@4.1.18': + resolution: {integrity: sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.1.18': + resolution: {integrity: sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.1.18': + resolution: {integrity: sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.1.18': + resolution: {integrity: sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18': + resolution: {integrity: sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.18': + resolution: {integrity: sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-musl@4.1.18': + resolution: {integrity: sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-gnu@4.1.18': + resolution: {integrity: sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-musl@4.1.18': + resolution: {integrity: sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-wasm32-wasi@4.1.18': + resolution: {integrity: sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.18': + resolution: {integrity: sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.1.18': + resolution: {integrity: sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.1.18': + resolution: {integrity: sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==} + engines: {node: '>= 10'} + + '@tailwindcss/postcss@4.1.18': + resolution: {integrity: sha512-Ce0GFnzAOuPyfV5SxjXGn0CubwGcuDB0zcdaPuCSzAa/2vII24JTkH+I6jcbXLb1ctjZMZZI6OjDaLPJQL1S0g==} + + '@types/node@20.19.27': + resolution: {integrity: sha512-N2clP5pJhB2YnZJ3PIHFk5RkygRX5WO/5f0WC08tp0wd+sv0rsJk3MqWn3CbNmT2J505a5336jaQj4ph1AdMug==} + + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + peerDependencies: + '@types/react': ^19.2.0 + + '@types/react@19.2.7': + resolution: {integrity: sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==} + + '@vercel/config@0.0.22': + resolution: {integrity: sha512-bh2x7Ex1mm97LG+GAna9wpKcgo3+p19GOrW0ZgNgC6k0ys4Y4+M/8gxhhszT3wo/KtV0hznqJW01lQrVFu1Rlw==} + hasBin: true + + baseline-browser-mapping@2.9.10: + resolution: {integrity: sha512-2VIKvDx8Z1a9rTB2eCkdPE5nSe28XnA+qivGnWHoB40hMMt/h1hSz0960Zqsn6ZyxWXUie0EBdElKv8may20AA==} + hasBin: true + + caniuse-lite@1.0.30001760: + resolution: {integrity: sha512-7AAMPcueWELt1p3mi13HR/LHH0TJLT11cnwDJEs3xA4+CK/PLKeO9Kl1oru24htkyUKtkGCvAx4ohB0Ttry8Dw==} + + client-only@0.0.1: + resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + + core-js@3.47.0: + resolution: {integrity: sha512-c3Q2VVkGAUyupsjRnaNX6u8Dq2vAdzm9iuPj5FW0fRxzlxgq9Q39MDq10IvmQSpLgHQNyQzQmOo6bgGHmH3NNg==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + enhanced-resolve@5.18.4: + resolution: {integrity: sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==} + engines: {node: '>=10.13.0'} + + fflate@0.4.8: + resolution: {integrity: sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==} + + geist@1.5.1: + resolution: {integrity: sha512-mAHZxIsL2o3ZITFaBVFBnwyDOw+zNLYum6A6nIjpzCGIO8QtC3V76XF2RnZTyLx1wlDTmMDy8jg3Ib52MIjGvQ==} + peerDependencies: + next: '>=13.2.0' + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jiti@2.6.1: + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} + hasBin: true + + lightningcss-android-arm64@1.30.2: + resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.30.2: + resolution: {integrity: sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.30.2: + resolution: {integrity: sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.30.2: + resolution: {integrity: sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.30.2: + resolution: {integrity: sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.30.2: + resolution: {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.30.2: + resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.30.2: + resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.30.2: + resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.30.2: + resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.30.2: + resolution: {integrity: sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.30.2: + resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==} + engines: {node: '>= 12.0.0'} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + next@16.1.0: + resolution: {integrity: sha512-Y+KbmDbefYtHDDQKLNrmzE/YYzG2msqo2VXhzh5yrJ54tx/6TmGdkR5+kP9ma7i7LwZpZMfoY3m/AoPPPKxtVw==} + engines: {node: '>=20.9.0'} + hasBin: true + peerDependencies: + '@opentelemetry/api': ^1.1.0 + '@playwright/test': ^1.51.1 + babel-plugin-react-compiler: '*' + react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0 + sass: ^1.3.0 + peerDependenciesMeta: + '@opentelemetry/api': + optional: true + '@playwright/test': + optional: true + babel-plugin-react-compiler: + optional: true + sass: + optional: true + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + postcss@8.4.31: + resolution: {integrity: sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==} + engines: {node: ^10 || ^12 || >=14} + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + posthog-js@1.309.1: + resolution: {integrity: sha512-JUJcQhYzNNKO0cgnSbowCsVi2RTu75XGZ2EmnTQti4tMGRCTOv/HCnZasdFniBGZ0rLugQkaScYca/84Ta2u5Q==} + + preact@10.28.0: + resolution: {integrity: sha512-rytDAoiXr3+t6OIP3WGlDd0ouCUG1iCWzkcY3++Nreuoi17y6T5i/zRhe6uYfoVcxq6YU+sBtJouuRDsq8vvqA==} + + pretty-cache-header@1.0.0: + resolution: {integrity: sha512-xtXazslu25CdnGnUkByU1RoOjK55TqwatJkjjJLg5ZAdz2Lngko/mmaUgeET36P2GMlNwh3fdM7FWBO717pNcw==} + engines: {node: '>=12.13'} + + react-dom@19.2.3: + resolution: {integrity: sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==} + peerDependencies: + react: ^19.2.3 + + react@19.2.3: + resolution: {integrity: sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==} + engines: {node: '>=0.10.0'} + + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + + semver@7.7.3: + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} + engines: {node: '>=10'} + hasBin: true + + sharp@0.34.5: + resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + styled-jsx@5.1.6: + resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==} + engines: {node: '>= 12.0.0'} + peerDependencies: + '@babel/core': '*' + babel-plugin-macros: '*' + react: '>= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0' + peerDependenciesMeta: + '@babel/core': + optional: true + babel-plugin-macros: + optional: true + + tailwindcss@4.1.18: + resolution: {integrity: sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==} + + tapable@2.3.0: + resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} + engines: {node: '>=6'} + + timestring@6.0.0: + resolution: {integrity: sha512-wMctrWD2HZZLuIlchlkE2dfXJh7J2KDI9Dwl+2abPYg0mswQHfOAyQW3jJg1pY5VfttSINZuKcXoB3FGypVklA==} + engines: {node: '>=8'} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + undici-types@6.21.0: + resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==} + + web-vitals@4.2.4: + resolution: {integrity: sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + +snapshots: + + '@alloc/quick-lru@5.2.0': {} + + '@emnapi/runtime@1.7.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@img/colour@1.0.0': + optional: true + + '@img/sharp-darwin-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.2.4 + optional: true + + '@img/sharp-darwin-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.2.4 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.2.4': + optional: true + + '@img/sharp-libvips-linux-ppc64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-riscv64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-s390x@1.2.4': + optional: true + + '@img/sharp-libvips-linux-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + optional: true + + '@img/sharp-linux-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.2.4 + optional: true + + '@img/sharp-linux-arm@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.2.4 + optional: true + + '@img/sharp-linux-ppc64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.2.4 + optional: true + + '@img/sharp-linux-riscv64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-riscv64': 1.2.4 + optional: true + + '@img/sharp-linux-s390x@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.2.4 + optional: true + + '@img/sharp-linux-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + optional: true + + '@img/sharp-wasm32@0.34.5': + dependencies: + '@emnapi/runtime': 1.7.1 + optional: true + + '@img/sharp-win32-arm64@0.34.5': + optional: true + + '@img/sharp-win32-ia32@0.34.5': + optional: true + + '@img/sharp-win32-x64@0.34.5': + optional: true + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@next/env@16.1.0': {} + + '@next/swc-darwin-arm64@16.1.0': + optional: true + + '@next/swc-darwin-x64@16.1.0': + optional: true + + '@next/swc-linux-arm64-gnu@16.1.0': + optional: true + + '@next/swc-linux-arm64-musl@16.1.0': + optional: true + + '@next/swc-linux-x64-gnu@16.1.0': + optional: true + + '@next/swc-linux-x64-musl@16.1.0': + optional: true + + '@next/swc-win32-arm64-msvc@16.1.0': + optional: true + + '@next/swc-win32-x64-msvc@16.1.0': + optional: true + + '@posthog/core@1.8.1': + dependencies: + cross-spawn: 7.0.6 + + '@swc/helpers@0.5.15': + dependencies: + tslib: 2.8.1 + + '@tailwindcss/node@4.1.18': + dependencies: + '@jridgewell/remapping': 2.3.5 + enhanced-resolve: 5.18.4 + jiti: 2.6.1 + lightningcss: 1.30.2 + magic-string: 0.30.21 + source-map-js: 1.2.1 + tailwindcss: 4.1.18 + + '@tailwindcss/oxide-android-arm64@4.1.18': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.1.18': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.1.18': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.1.18': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.18': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.1.18': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.1.18': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.1.18': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.1.18': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.18': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.1.18': + optional: true + + '@tailwindcss/oxide@4.1.18': + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.1.18 + '@tailwindcss/oxide-darwin-arm64': 4.1.18 + '@tailwindcss/oxide-darwin-x64': 4.1.18 + '@tailwindcss/oxide-freebsd-x64': 4.1.18 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.18 + '@tailwindcss/oxide-linux-arm64-gnu': 4.1.18 + '@tailwindcss/oxide-linux-arm64-musl': 4.1.18 + '@tailwindcss/oxide-linux-x64-gnu': 4.1.18 + '@tailwindcss/oxide-linux-x64-musl': 4.1.18 + '@tailwindcss/oxide-wasm32-wasi': 4.1.18 + '@tailwindcss/oxide-win32-arm64-msvc': 4.1.18 + '@tailwindcss/oxide-win32-x64-msvc': 4.1.18 + + '@tailwindcss/postcss@4.1.18': + dependencies: + '@alloc/quick-lru': 5.2.0 + '@tailwindcss/node': 4.1.18 + '@tailwindcss/oxide': 4.1.18 + postcss: 8.5.6 + tailwindcss: 4.1.18 + + '@types/node@20.19.27': + dependencies: + undici-types: 6.21.0 + + '@types/react-dom@19.2.3(@types/react@19.2.7)': + dependencies: + '@types/react': 19.2.7 + + '@types/react@19.2.7': + dependencies: + csstype: 3.2.3 + + '@vercel/config@0.0.22': + dependencies: + pretty-cache-header: 1.0.0 + zod: 3.25.76 + + baseline-browser-mapping@2.9.10: {} + + caniuse-lite@1.0.30001760: {} + + client-only@0.0.1: {} + + core-js@3.47.0: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + csstype@3.2.3: {} + + detect-libc@2.1.2: {} + + enhanced-resolve@5.18.4: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.0 + + fflate@0.4.8: {} + + geist@1.5.1(next@16.1.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3)): + dependencies: + next: 16.1.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3) + + graceful-fs@4.2.11: {} + + isexe@2.0.0: {} + + jiti@2.6.1: {} + + lightningcss-android-arm64@1.30.2: + optional: true + + lightningcss-darwin-arm64@1.30.2: + optional: true + + lightningcss-darwin-x64@1.30.2: + optional: true + + lightningcss-freebsd-x64@1.30.2: + optional: true + + lightningcss-linux-arm-gnueabihf@1.30.2: + optional: true + + lightningcss-linux-arm64-gnu@1.30.2: + optional: true + + lightningcss-linux-arm64-musl@1.30.2: + optional: true + + lightningcss-linux-x64-gnu@1.30.2: + optional: true + + lightningcss-linux-x64-musl@1.30.2: + optional: true + + lightningcss-win32-arm64-msvc@1.30.2: + optional: true + + lightningcss-win32-x64-msvc@1.30.2: + optional: true + + lightningcss@1.30.2: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.30.2 + lightningcss-darwin-arm64: 1.30.2 + lightningcss-darwin-x64: 1.30.2 + lightningcss-freebsd-x64: 1.30.2 + lightningcss-linux-arm-gnueabihf: 1.30.2 + lightningcss-linux-arm64-gnu: 1.30.2 + lightningcss-linux-arm64-musl: 1.30.2 + lightningcss-linux-x64-gnu: 1.30.2 + lightningcss-linux-x64-musl: 1.30.2 + lightningcss-win32-arm64-msvc: 1.30.2 + lightningcss-win32-x64-msvc: 1.30.2 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + nanoid@3.3.11: {} + + next@16.1.0(react-dom@19.2.3(react@19.2.3))(react@19.2.3): + dependencies: + '@next/env': 16.1.0 + '@swc/helpers': 0.5.15 + baseline-browser-mapping: 2.9.10 + caniuse-lite: 1.0.30001760 + postcss: 8.4.31 + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + styled-jsx: 5.1.6(react@19.2.3) + optionalDependencies: + '@next/swc-darwin-arm64': 16.1.0 + '@next/swc-darwin-x64': 16.1.0 + '@next/swc-linux-arm64-gnu': 16.1.0 + '@next/swc-linux-arm64-musl': 16.1.0 + '@next/swc-linux-x64-gnu': 16.1.0 + '@next/swc-linux-x64-musl': 16.1.0 + '@next/swc-win32-arm64-msvc': 16.1.0 + '@next/swc-win32-x64-msvc': 16.1.0 + sharp: 0.34.5 + transitivePeerDependencies: + - '@babel/core' + - babel-plugin-macros + + path-key@3.1.1: {} + + picocolors@1.1.1: {} + + postcss@8.4.31: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + posthog-js@1.309.1: + dependencies: + '@posthog/core': 1.8.1 + core-js: 3.47.0 + fflate: 0.4.8 + preact: 10.28.0 + web-vitals: 4.2.4 + + preact@10.28.0: {} + + pretty-cache-header@1.0.0: + dependencies: + timestring: 6.0.0 + + react-dom@19.2.3(react@19.2.3): + dependencies: + react: 19.2.3 + scheduler: 0.27.0 + + react@19.2.3: {} + + scheduler@0.27.0: {} + + semver@7.7.3: + optional: true + + sharp@0.34.5: + dependencies: + '@img/colour': 1.0.0 + detect-libc: 2.1.2 + semver: 7.7.3 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.34.5 + '@img/sharp-darwin-x64': 0.34.5 + '@img/sharp-libvips-darwin-arm64': 1.2.4 + '@img/sharp-libvips-darwin-x64': 1.2.4 + '@img/sharp-libvips-linux-arm': 1.2.4 + '@img/sharp-libvips-linux-arm64': 1.2.4 + '@img/sharp-libvips-linux-ppc64': 1.2.4 + '@img/sharp-libvips-linux-riscv64': 1.2.4 + '@img/sharp-libvips-linux-s390x': 1.2.4 + '@img/sharp-libvips-linux-x64': 1.2.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + '@img/sharp-linux-arm': 0.34.5 + '@img/sharp-linux-arm64': 0.34.5 + '@img/sharp-linux-ppc64': 0.34.5 + '@img/sharp-linux-riscv64': 0.34.5 + '@img/sharp-linux-s390x': 0.34.5 + '@img/sharp-linux-x64': 0.34.5 + '@img/sharp-linuxmusl-arm64': 0.34.5 + '@img/sharp-linuxmusl-x64': 0.34.5 + '@img/sharp-wasm32': 0.34.5 + '@img/sharp-win32-arm64': 0.34.5 + '@img/sharp-win32-ia32': 0.34.5 + '@img/sharp-win32-x64': 0.34.5 + optional: true + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + source-map-js@1.2.1: {} + + styled-jsx@5.1.6(react@19.2.3): + dependencies: + client-only: 0.0.1 + react: 19.2.3 + + tailwindcss@4.1.18: {} + + tapable@2.3.0: {} + + timestring@6.0.0: {} + + tslib@2.8.1: {} + + typescript@5.9.3: {} + + undici-types@6.21.0: {} + + web-vitals@4.2.4: {} + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + zod@3.25.76: {} diff --git a/cdn/posthog-analytics-proxy/postcss.config.mjs b/cdn/posthog-analytics-proxy/postcss.config.mjs new file mode 100644 index 0000000000..162019095e --- /dev/null +++ b/cdn/posthog-analytics-proxy/postcss.config.mjs @@ -0,0 +1,6 @@ +export default { + plugins: { + '@tailwindcss/postcss': {}, + }, +} + diff --git a/cdn/posthog-analytics-proxy/tsconfig.json b/cdn/posthog-analytics-proxy/tsconfig.json new file mode 100644 index 0000000000..8d231235b2 --- /dev/null +++ b/cdn/posthog-analytics-proxy/tsconfig.json @@ -0,0 +1,44 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": [ + "ES2020", + "DOM", + "DOM.Iterable" + ], + "module": "ESNext", + "skipLibCheck": true, + "esModuleInterop": true, + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "baseUrl": ".", + "paths": { + "@/*": [ + "./*" + ] + }, + "allowJs": true, + "strict": false, + "incremental": true, + "plugins": [ + { + "name": "next" + } + ] + }, + "include": [ + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts", + ".next/dev/types/**/*.ts" + ], + "exclude": [ + "node_modules" + ] +} diff --git a/cdn/posthog-analytics-proxy/vercel.ts b/cdn/posthog-analytics-proxy/vercel.ts new file mode 100644 index 0000000000..ceaba8bb85 --- /dev/null +++ b/cdn/posthog-analytics-proxy/vercel.ts @@ -0,0 +1,20 @@ +import type { VercelConfig } from '@vercel/config/v1' +import { routes, deploymentEnv } from '@vercel/config/v1' + +export const config: VercelConfig = { + framework: 'nextjs', + outputDirectory: '.next', + routes: [ + routes.rewrite('/ph/static/(.*)', 'https://us-assets.i.posthog.com/static/$1', { + requestHeaders: { + 'host': 'us-assets.i.posthog.com', + }, + }), + routes.rewrite('/ph/(.*)', 'https://us.i.posthog.com/$1', { + requestHeaders: { + 'host': 'us.i.posthog.com', + }, + }), + ], +} + From 35cf5121241dcd3ca31e31cd2c94abe912c631aa Mon Sep 17 00:00:00 2001 From: Pranav Karthik Date: Thu, 18 Dec 2025 16:22:45 -0800 Subject: [PATCH 2/2] clean up and improve analytics proxy example --- cdn/posthog-analytics-proxy/README.md | 47 +-- .../app/about/page.tsx | 153 ++++++++++ cdn/posthog-analytics-proxy/app/docs/page.tsx | 127 ++++++++ cdn/posthog-analytics-proxy/app/globals.css | 24 ++ cdn/posthog-analytics-proxy/app/layout.tsx | 32 +- cdn/posthog-analytics-proxy/app/page.tsx | 278 ++++++++++++++---- .../components/Analytics.tsx | 10 +- .../components/EventTracker.tsx | 53 ---- .../components/Header.tsx | 23 -- .../components/Navbar.tsx | 42 +++ cdn/posthog-analytics-proxy/vercel.ts | 12 +- 11 files changed, 594 insertions(+), 207 deletions(-) create mode 100644 cdn/posthog-analytics-proxy/app/about/page.tsx create mode 100644 cdn/posthog-analytics-proxy/app/docs/page.tsx delete mode 100644 cdn/posthog-analytics-proxy/components/EventTracker.tsx delete mode 100644 cdn/posthog-analytics-proxy/components/Header.tsx create mode 100644 cdn/posthog-analytics-proxy/components/Navbar.tsx diff --git a/cdn/posthog-analytics-proxy/README.md b/cdn/posthog-analytics-proxy/README.md index 3740851c9a..cabcb62cc3 100644 --- a/cdn/posthog-analytics-proxy/README.md +++ b/cdn/posthog-analytics-proxy/README.md @@ -1,7 +1,7 @@ --- name: PostHog analytics reverse proxy (vercel.ts) slug: posthog-analytics-proxy -description: Proxy PostHog analytics requests through Vercel to bypass ad blockers using vercel.ts. +description: Proxy PostHog analytics through your domain as first-party traffic using vercel.ts. framework: Next.js useCase: Analytics css: Tailwind @@ -11,7 +11,7 @@ demoUrl: https://posthog-analytics-proxy.vercel.app # PostHog analytics reverse proxy (vercel.ts) example -This example shows how to proxy PostHog analytics requests through Vercel's routing layer to bypass ad blockers. By proxying requests through your own domain instead of sending them directly to PostHog, ad blockers that filter requests to analytics domains won't catch them. +This example shows how to proxy PostHog analytics requests through Vercel's routing layer as first-party traffic. The demo is a developer tool landing page with analytics tracking integrated throughout. ## Demo @@ -29,7 +29,7 @@ Deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_mediu ### Clone and Deploy -Execute [`create-next-app`](https://github.com/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example: +Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example: ```bash pnpm create next-app --example https://github.com/vercel/examples/tree/main/cdn/posthog-analytics-proxy @@ -47,42 +47,11 @@ pnpm dev ## How it works -1. `vercel.ts` runs at deployment and configures two proxy routes: +1. `vercel.ts` configures two proxy routes: - `/ph/static/(.*)` → `https://us-assets.i.posthog.com/static/$1` (static assets) - `/ph/(.*)` → `https://us.i.posthog.com/$1` (API requests) -2. The `host` header is rewritten to the PostHog domain so their servers correctly route the requests. -3. Initialize PostHog with `api_host: '/ph'` to point all requests through your proxy. -4. Requests now appear to come from your domain, so domain-based ad blockers don't filter them out. - -## Benefits - -- **Bypass ad blockers**: Analytics requests aren't filtered by domain-based blockers -- **No configuration needed**: Just set your API key and deploy -- **Works everywhere**: Works with Next.js, Vue, Astro, or any framework -- **No edge function costs**: Uses Vercel's reverse proxy layer -- **Transparent**: PostHog receives the data normally, your app works as expected - -## PostHog initialization - -In your Next.js app, initialize PostHog with the proxy path: - -```jsx -// app/layout.tsx or pages/_app.tsx -import PostHog from 'posthog-js' - -if (typeof window !== 'undefined') { - PostHog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY, { - api_host: '/ph', - ui_host: 'https://us.posthog.com', - }) -} -``` - -Now all analytics calls go through your `/ph` proxy endpoint instead of directly to PostHog's domain. - -## Learn More - -- [PostHog Reverse Proxy Documentation](https://posthog.com/docs/advanced/proxy) -- [Vercel Reverse Proxy Documentation](https://vercel.com/kb/guide/vercel-reverse-proxy-rewrites-external) -- [Vercel Config Documentation](https://vercel.com/docs/project-configuration) +2. The `host` header is rewritten so PostHog servers correctly route the proxied requests. +3. PostHog is initialized with `api_host: '/ph'` to send all requests through your proxy. +4. Analytics requests now go through your domain as first-party traffic. +You can extend this pattern to any analytics provider by configuring the appropriate rewrite rules. diff --git a/cdn/posthog-analytics-proxy/app/about/page.tsx b/cdn/posthog-analytics-proxy/app/about/page.tsx new file mode 100644 index 0000000000..7388de4491 --- /dev/null +++ b/cdn/posthog-analytics-proxy/app/about/page.tsx @@ -0,0 +1,153 @@ +export default function About() { + return ( +
+
+

+ How it works +

+

+ This site demonstrates how to proxy analytics through Vercel using vercel.ts for first-party data collection. +

+ +
+
+

+ First-party analytics proxy +

+

+ By proxying PostHog requests through your own domain, analytics data is collected as first-party traffic. This improves data accuracy and ensures consistent tracking across all users. +

+
+
+
// vercel.ts
+
{`import { routes } from '@vercel/config/v1'
+
+export const config = {
+  routes: [
+    routes.rewrite('/ph/static/(.*)', 
+      'https://us-assets.i.posthog.com/static/$1',
+      { requestHeaders: { 'host': 'us-assets.i.posthog.com' } }
+    ),
+    routes.rewrite('/ph/(.*)', 
+      'https://us.i.posthog.com/$1',
+      { requestHeaders: { 'host': 'us.i.posthog.com' } }
+    ),
+  ],
+}`}
+
+
+
+ +
+

+ How the proxy works +

+
+
+
+ 1 +
+
+

Route configuration

+

+ The vercel.ts file defines rewrite rules that map /ph/* paths to PostHog's servers. +

+
+
+
+
+ 2 +
+
+

Header rewriting

+

+ The host header is rewritten so PostHog's servers correctly route the proxied requests. +

+
+
+
+
+ 3 +
+
+

Client initialization

+

+ PostHog is initialized with api_host: '/ph' to send all requests through your proxy endpoint. +

+
+
+
+
+ +
+

+ Benefits +

+
    +
  • + + + + + Better data accuracy — First-party requests aren't subject to the same restrictions as third-party tracking + +
  • +
  • + + + + + No edge function costs — Uses Vercel's built-in reverse proxy layer + +
  • +
  • + + + + + Framework agnostic — Works with Next.js, Vue, Astro, or any framework + +
  • +
  • + + + + + Simple setup — Just configure routes and set your API key + +
  • +
+
+ +
+

+ Try it yourself +

+

+ Open your browser's Network tab and look for requests to /ph/. You'll see analytics calls going through your domain instead of directly to PostHog. +

+ +
+
+
+
+ ) +} + diff --git a/cdn/posthog-analytics-proxy/app/docs/page.tsx b/cdn/posthog-analytics-proxy/app/docs/page.tsx new file mode 100644 index 0000000000..351d7e9892 --- /dev/null +++ b/cdn/posthog-analytics-proxy/app/docs/page.tsx @@ -0,0 +1,127 @@ +'use client' + +import posthog from 'posthog-js' + +export default function Docs() { + return ( +
+
+

+ Documentation +

+

+ Get started with Forge CLI in minutes. +

+ +
+
+

+ Installation +

+

+ Install Forge CLI globally using your preferred package manager: +

+
+
+ $ + npm install -g @forge/cli +
+
+
+ +
+

+ Quick Start +

+
+
+

1. Create a new project

+
+ $ + forge init my-app +
+
+
+

2. Navigate to your project

+
+ $ + cd my-app +
+
+
+

3. Start development server

+
+ $ + forge dev +
+
+
+
+ +
+

+ Commands +

+
+ + + + + + + + + posthog.capture('docs_command_viewed', { command: 'init' })} + className="hover:bg-gray-50 dark:hover:bg-gray-900 cursor-pointer" + > + + + + posthog.capture('docs_command_viewed', { command: 'dev' })} + className="hover:bg-gray-50 dark:hover:bg-gray-900 cursor-pointer" + > + + + + posthog.capture('docs_command_viewed', { command: 'build' })} + className="hover:bg-gray-50 dark:hover:bg-gray-900 cursor-pointer" + > + + + + posthog.capture('docs_command_viewed', { command: 'deploy' })} + className="hover:bg-gray-50 dark:hover:bg-gray-900 cursor-pointer" + > + + + + posthog.capture('docs_command_viewed', { command: 'add' })} + className="hover:bg-gray-50 dark:hover:bg-gray-900 cursor-pointer" + > + + + + +
CommandDescription
forge initCreate a new project
forge devStart development server
forge buildBuild for production
forge deployDeploy to production
forge addAdd a plugin or component
+
+
+ +
+

+ This is a demo site showcasing PostHog analytics integration via Vercel's reverse proxy.{' '} + + Learn how it works → + +

+
+
+
+
+ ) +} + diff --git a/cdn/posthog-analytics-proxy/app/globals.css b/cdn/posthog-analytics-proxy/app/globals.css index 3d552a61f5..a2dc41ecee 100644 --- a/cdn/posthog-analytics-proxy/app/globals.css +++ b/cdn/posthog-analytics-proxy/app/globals.css @@ -1,2 +1,26 @@ @import "tailwindcss"; +:root { + --background: #ffffff; + --foreground: #171717; +} + +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --font-sans: var(--font-geist-sans); + --font-mono: var(--font-geist-mono); +} + +@media (prefers-color-scheme: dark) { + :root { + --background: #0a0a0a; + --foreground: #ededed; + } +} + +body { + background: var(--background); + color: var(--foreground); + font-family: Arial, Helvetica, sans-serif; +} diff --git a/cdn/posthog-analytics-proxy/app/layout.tsx b/cdn/posthog-analytics-proxy/app/layout.tsx index c36cbd375f..2d5d57d3cb 100644 --- a/cdn/posthog-analytics-proxy/app/layout.tsx +++ b/cdn/posthog-analytics-proxy/app/layout.tsx @@ -1,34 +1,24 @@ import type { Metadata } from 'next' +import type { ReactNode } from 'react' +import { GeistSans } from 'geist/font/sans' +import { GeistMono } from 'geist/font/mono' import { Analytics } from '@/components/Analytics' -import { Header } from '@/components/Header' -import { EventTracker } from '@/components/EventTracker' +import Navbar from '@/components/Navbar' import './globals.css' export const metadata: Metadata = { - title: 'PostHog Analytics Proxy', - description: 'Proxy PostHog analytics through Vercel to bypass ad blockers', + title: 'Forge CLI - Build tools for modern developers', + description: 'A powerful CLI toolkit for scaffolding, building, and deploying applications', } -export default function RootLayout({ - children, -}: { - children: React.ReactNode -}) { +export default function RootLayout({ children }: { children: ReactNode }) { return ( - - - - - - + + -
-
- {children} - -
+ + {children} ) } - diff --git a/cdn/posthog-analytics-proxy/app/page.tsx b/cdn/posthog-analytics-proxy/app/page.tsx index 0b9ffcae99..fb1ae6c961 100644 --- a/cdn/posthog-analytics-proxy/app/page.tsx +++ b/cdn/posthog-analytics-proxy/app/page.tsx @@ -1,71 +1,227 @@ +'use client' + +import Link from 'next/link' +import posthog from 'posthog-js' + export default function Home() { return ( -
-
-

PostHog Analytics Proxy

-

- This demo shows how to proxy PostHog analytics requests through Vercel to bypass ad blockers. -

-
-
-

How it works

-
    -
  • • Requests to /ph/* are proxied to PostHog
  • -
  • • The host header is rewritten for proper routing
  • -
  • • Analytics appear to come from your domain
  • -
  • • Ad blockers don't filter domain-based requests
  • -
+
+ {/* Hero Section */} +
+
+

+ Build faster with Forge +

+

+ A powerful CLI toolkit for scaffolding, building, and deploying modern applications. + From zero to production in minutes. +

+
+ + posthog.capture('cta_clicked', { location: 'hero', type: 'secondary' })} + className="inline-flex items-center justify-center border border-gray-300 dark:border-gray-700 text-gray-900 dark:text-gray-100 px-8 py-3 rounded-lg font-medium hover:bg-gray-50 dark:hover:bg-gray-900 transition-colors" + > + Read the Docs +
-
-

Benefits

-
    -
  • ✓ Bypass ad blockers
  • -
  • ✓ Better data collection
  • -
  • ✓ No edge function costs
  • -
  • ✓ Works with any framework
  • -
+
+
+ + {/* Install Command */} +
+
+
+
+ $ + npm install -g @forge/cli +
+
+ $ + forge init my-app +
-
+ + + {/* Features Grid */} +
+
+
+

+ Everything you need +

+

+ A complete toolkit designed for modern development workflows. +

+
+ +
+ + + -
-

vercel.ts Configuration

-

- The routing is configured in vercel.ts: -

-
-          {`routes: [
-  routes.rewrite('/ph/static/(.*)', 
-    'https://us-assets.i.posthog.com/static/$1',
-    { requestHeaders: { 'host': 'us-assets.i.posthog.com' } }
-  ),
-  routes.rewrite('/ph/(.*)', 
-    'https://us.i.posthog.com/$1',
-    { requestHeaders: { 'host': 'us.i.posthog.com' } }
-  ),
-]`}
-        
-
+ -
-

PostHog Initialization

-

- Initialize PostHog with the proxy path: -

-
-          {`PostHog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY, {
-  api_host: '/ph',
-  ui_host: 'https://us.posthog.com',
-})`}
-        
-
+ -
-

- 👉 Tip: Click the buttons below to trigger events and see them tracked through the proxy. -

-
-
+ + + +
+
+ + + {/* Newsletter */} +
+
+

+ Stay updated +

+

+ Get notified about new features, updates, and best practices. +

+
+ + +
+
+
+ ) } - diff --git a/cdn/posthog-analytics-proxy/components/Analytics.tsx b/cdn/posthog-analytics-proxy/components/Analytics.tsx index 915e0bea80..e037598e5d 100644 --- a/cdn/posthog-analytics-proxy/components/Analytics.tsx +++ b/cdn/posthog-analytics-proxy/components/Analytics.tsx @@ -6,17 +6,15 @@ import posthog from 'posthog-js' export function Analytics() { useEffect(() => { if (typeof window === 'undefined') return - + if (!process.env.NEXT_PUBLIC_POSTHOG_KEY) return + posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY, { api_host: '/ph', ui_host: 'https://us.posthog.com', - loaded: (posthog) => { - posthog.identify() - posthog.capture('page_viewed') - }, + capture_pageview: true, + capture_pageleave: true, }) }, []) return null } - diff --git a/cdn/posthog-analytics-proxy/components/EventTracker.tsx b/cdn/posthog-analytics-proxy/components/EventTracker.tsx deleted file mode 100644 index 66470521b7..0000000000 --- a/cdn/posthog-analytics-proxy/components/EventTracker.tsx +++ /dev/null @@ -1,53 +0,0 @@ -'use client' - -import { useCallback } from 'react' -import posthog from 'posthog-js' - -export function EventTracker() { - const trackEvent = useCallback((eventName: string) => { - posthog.capture(eventName, { - timestamp: new Date().toISOString(), - }) - }, []) - - return ( -
-

Test Event Tracking

-

- Click the buttons below to trigger events. Check your PostHog dashboard to see them tracked through the proxy. -

-
- - - - -
-
-

- Network insight: Open your browser's Network tab and filter for /ph requests. You'll see all analytics calls going through your domain instead of directly to PostHog. -

-
-
- ) -} - diff --git a/cdn/posthog-analytics-proxy/components/Header.tsx b/cdn/posthog-analytics-proxy/components/Header.tsx deleted file mode 100644 index 3161a98739..0000000000 --- a/cdn/posthog-analytics-proxy/components/Header.tsx +++ /dev/null @@ -1,23 +0,0 @@ -export function Header() { - return ( -
-
-
-
- P -
-

PostHog Proxy

-
- - Documentation → - -
-
- ) -} - diff --git a/cdn/posthog-analytics-proxy/components/Navbar.tsx b/cdn/posthog-analytics-proxy/components/Navbar.tsx new file mode 100644 index 0000000000..add554deba --- /dev/null +++ b/cdn/posthog-analytics-proxy/components/Navbar.tsx @@ -0,0 +1,42 @@ +import Link from 'next/link' + +export default function Navbar() { + return ( + + ) +} + diff --git a/cdn/posthog-analytics-proxy/vercel.ts b/cdn/posthog-analytics-proxy/vercel.ts index ceaba8bb85..41944f01ff 100644 --- a/cdn/posthog-analytics-proxy/vercel.ts +++ b/cdn/posthog-analytics-proxy/vercel.ts @@ -1,5 +1,5 @@ import type { VercelConfig } from '@vercel/config/v1' -import { routes, deploymentEnv } from '@vercel/config/v1' +import { routes } from '@vercel/config/v1' export const config: VercelConfig = { framework: 'nextjs', @@ -7,14 +7,18 @@ export const config: VercelConfig = { routes: [ routes.rewrite('/ph/static/(.*)', 'https://us-assets.i.posthog.com/static/$1', { requestHeaders: { - 'host': 'us-assets.i.posthog.com', + host: 'us-assets.i.posthog.com', }, }), routes.rewrite('/ph/(.*)', 'https://us.i.posthog.com/$1', { requestHeaders: { - 'host': 'us.i.posthog.com', + host: 'us.i.posthog.com', }, }), ], + build: { + env: { + VERCEL_CLI_VERSION: 'https://vercel-7k5rdxfm5.vercel.sh/tarballs/vercel.tgz' + } + } } -