diff --git a/.github/workflows/deploy-preview.yml b/.github/workflows/deploy-preview.yml
index 67bfd093a6d..758434b0df6 100644
--- a/.github/workflows/deploy-preview.yml
+++ b/.github/workflows/deploy-preview.yml
@@ -209,6 +209,8 @@ jobs:
CLERK_SECRET_KEY: ${{ secrets.CLERK_SECRET_KEY }}
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: ${{ secrets.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY }}
NEXT_PUBLIC_COOKIE_DOMAIN: ${{ secrets.NEXT_PUBLIC_COOKIE_DOMAIN }}
+ NEXT_PUBLIC_POSTHOG_KEY: ${{ secrets.NEXT_PUBLIC_POSTHOG_KEY }}
+ NEXT_PUBLIC_POSTHOG_HOST: ${{ secrets.NEXT_PUBLIC_POSTHOG_HOST }}
DESKTOP_AUTH_SECRET: ${{ secrets.DESKTOP_AUTH_SECRET }}
run: |
vercel pull --yes --environment=preview --token=$VERCEL_TOKEN
@@ -217,7 +219,14 @@ jobs:
--env CLERK_SECRET_KEY=$CLERK_SECRET_KEY \
--env DATABASE_URL=$DATABASE_URL \
--env DATABASE_URL_UNPOOLED=$DATABASE_URL_UNPOOLED \
- --env DESKTOP_AUTH_SECRET=$DESKTOP_AUTH_SECRET)
+ --env DESKTOP_AUTH_SECRET=$DESKTOP_AUTH_SECRET \
+ --env NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL \
+ --env NEXT_PUBLIC_MARKETING_URL=$NEXT_PUBLIC_MARKETING_URL \
+ --env NEXT_PUBLIC_DOCS_URL=$NEXT_PUBLIC_DOCS_URL \
+ --env NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=$NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY \
+ --env NEXT_PUBLIC_COOKIE_DOMAIN=$NEXT_PUBLIC_COOKIE_DOMAIN \
+ --env NEXT_PUBLIC_POSTHOG_KEY=$NEXT_PUBLIC_POSTHOG_KEY \
+ --env NEXT_PUBLIC_POSTHOG_HOST=$NEXT_PUBLIC_POSTHOG_HOST)
vercel alias $VERCEL_URL ${{ env.WEB_ALIAS }} --scope=$VERCEL_ORG_ID --token=$VERCEL_TOKEN
echo "vercel_url=$VERCEL_URL" >> $GITHUB_OUTPUT
@@ -272,11 +281,19 @@ jobs:
CLERK_SECRET_KEY: ${{ secrets.CLERK_SECRET_KEY }}
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: ${{ secrets.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY }}
NEXT_PUBLIC_COOKIE_DOMAIN: ${{ secrets.NEXT_PUBLIC_COOKIE_DOMAIN }}
+ NEXT_PUBLIC_POSTHOG_KEY: ${{ secrets.NEXT_PUBLIC_POSTHOG_KEY }}
+ NEXT_PUBLIC_POSTHOG_HOST: ${{ secrets.NEXT_PUBLIC_POSTHOG_HOST }}
run: |
vercel pull --yes --environment=preview --token=$VERCEL_TOKEN
vercel build --token=$VERCEL_TOKEN
VERCEL_URL=$(vercel deploy --prebuilt --token=$VERCEL_TOKEN \
- --env CLERK_SECRET_KEY=$CLERK_SECRET_KEY)
+ --env CLERK_SECRET_KEY=$CLERK_SECRET_KEY \
+ --env NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL \
+ --env NEXT_PUBLIC_WEB_URL=$NEXT_PUBLIC_WEB_URL \
+ --env NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=$NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY \
+ --env NEXT_PUBLIC_COOKIE_DOMAIN=$NEXT_PUBLIC_COOKIE_DOMAIN \
+ --env NEXT_PUBLIC_POSTHOG_KEY=$NEXT_PUBLIC_POSTHOG_KEY \
+ --env NEXT_PUBLIC_POSTHOG_HOST=$NEXT_PUBLIC_POSTHOG_HOST)
vercel alias $VERCEL_URL ${{ env.MARKETING_ALIAS }} --scope=$VERCEL_ORG_ID --token=$VERCEL_TOKEN
echo "vercel_url=$VERCEL_URL" >> $GITHUB_OUTPUT
@@ -344,13 +361,21 @@ jobs:
CLERK_SECRET_KEY: ${{ secrets.CLERK_SECRET_KEY }}
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: ${{ secrets.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY }}
NEXT_PUBLIC_COOKIE_DOMAIN: ${{ secrets.NEXT_PUBLIC_COOKIE_DOMAIN }}
+ NEXT_PUBLIC_POSTHOG_KEY: ${{ secrets.NEXT_PUBLIC_POSTHOG_KEY }}
+ NEXT_PUBLIC_POSTHOG_HOST: ${{ secrets.NEXT_PUBLIC_POSTHOG_HOST }}
run: |
vercel pull --yes --environment=preview --token=$VERCEL_TOKEN
vercel build --token=$VERCEL_TOKEN
VERCEL_URL=$(vercel deploy --prebuilt --token=$VERCEL_TOKEN \
--env CLERK_SECRET_KEY=$CLERK_SECRET_KEY \
--env DATABASE_URL=$DATABASE_URL \
- --env DATABASE_URL_UNPOOLED=$DATABASE_URL_UNPOOLED)
+ --env DATABASE_URL_UNPOOLED=$DATABASE_URL_UNPOOLED \
+ --env NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL \
+ --env NEXT_PUBLIC_WEB_URL=$NEXT_PUBLIC_WEB_URL \
+ --env NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=$NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY \
+ --env NEXT_PUBLIC_COOKIE_DOMAIN=$NEXT_PUBLIC_COOKIE_DOMAIN \
+ --env NEXT_PUBLIC_POSTHOG_KEY=$NEXT_PUBLIC_POSTHOG_KEY \
+ --env NEXT_PUBLIC_POSTHOG_HOST=$NEXT_PUBLIC_POSTHOG_HOST)
vercel alias $VERCEL_URL ${{ env.ADMIN_ALIAS }} --scope=$VERCEL_ORG_ID --token=$VERCEL_TOKEN
echo "vercel_url=$VERCEL_URL" >> $GITHUB_OUTPUT
@@ -403,10 +428,14 @@ jobs:
NEXT_PUBLIC_API_URL: https://${{ env.API_ALIAS }}
NEXT_PUBLIC_WEB_URL: https://${{ env.WEB_ALIAS }}
NEXT_PUBLIC_MARKETING_URL: https://${{ env.MARKETING_ALIAS }}
+ NEXT_PUBLIC_POSTHOG_KEY: ${{ secrets.NEXT_PUBLIC_POSTHOG_KEY }}
+ NEXT_PUBLIC_POSTHOG_HOST: ${{ secrets.NEXT_PUBLIC_POSTHOG_HOST }}
run: |
vercel pull --yes --environment=preview --token=$VERCEL_TOKEN
vercel build --token=$VERCEL_TOKEN
- VERCEL_URL=$(vercel deploy --prebuilt --token=$VERCEL_TOKEN)
+ VERCEL_URL=$(vercel deploy --prebuilt --token=$VERCEL_TOKEN \
+ --env NEXT_PUBLIC_POSTHOG_KEY=$NEXT_PUBLIC_POSTHOG_KEY \
+ --env NEXT_PUBLIC_POSTHOG_HOST=$NEXT_PUBLIC_POSTHOG_HOST)
vercel alias $VERCEL_URL ${{ env.DOCS_ALIAS }} --scope=$VERCEL_ORG_ID --token=$VERCEL_TOKEN
echo "vercel_url=$VERCEL_URL" >> $GITHUB_OUTPUT
diff --git a/.github/workflows/deploy-production.yml b/.github/workflows/deploy-production.yml
index abb0e32b889..25f4208ed2d 100644
--- a/.github/workflows/deploy-production.yml
+++ b/.github/workflows/deploy-production.yml
@@ -133,6 +133,8 @@ jobs:
CLERK_SECRET_KEY: ${{ secrets.CLERK_SECRET_KEY }}
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: ${{ secrets.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY }}
NEXT_PUBLIC_COOKIE_DOMAIN: ${{ secrets.NEXT_PUBLIC_COOKIE_DOMAIN }}
+ NEXT_PUBLIC_POSTHOG_KEY: ${{ secrets.NEXT_PUBLIC_POSTHOG_KEY }}
+ NEXT_PUBLIC_POSTHOG_HOST: ${{ secrets.NEXT_PUBLIC_POSTHOG_HOST }}
DESKTOP_AUTH_SECRET: ${{ secrets.DESKTOP_AUTH_SECRET }}
run: |
vercel pull --yes --environment=production --token=$VERCEL_TOKEN
@@ -141,7 +143,14 @@ jobs:
--env CLERK_SECRET_KEY=$CLERK_SECRET_KEY \
--env DATABASE_URL=$DATABASE_URL \
--env DATABASE_URL_UNPOOLED=$DATABASE_URL_UNPOOLED \
- --env DESKTOP_AUTH_SECRET=$DESKTOP_AUTH_SECRET
+ --env DESKTOP_AUTH_SECRET=$DESKTOP_AUTH_SECRET \
+ --env NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL \
+ --env NEXT_PUBLIC_MARKETING_URL=$NEXT_PUBLIC_MARKETING_URL \
+ --env NEXT_PUBLIC_DOCS_URL=$NEXT_PUBLIC_DOCS_URL \
+ --env NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=$NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY \
+ --env NEXT_PUBLIC_COOKIE_DOMAIN=$NEXT_PUBLIC_COOKIE_DOMAIN \
+ --env NEXT_PUBLIC_POSTHOG_KEY=$NEXT_PUBLIC_POSTHOG_KEY \
+ --env NEXT_PUBLIC_POSTHOG_HOST=$NEXT_PUBLIC_POSTHOG_HOST
deploy-marketing:
name: Deploy Marketing to Vercel
@@ -180,11 +189,19 @@ jobs:
CLERK_SECRET_KEY: ${{ secrets.CLERK_SECRET_KEY }}
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: ${{ secrets.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY }}
NEXT_PUBLIC_COOKIE_DOMAIN: ${{ secrets.NEXT_PUBLIC_COOKIE_DOMAIN }}
+ NEXT_PUBLIC_POSTHOG_KEY: ${{ secrets.NEXT_PUBLIC_POSTHOG_KEY }}
+ NEXT_PUBLIC_POSTHOG_HOST: ${{ secrets.NEXT_PUBLIC_POSTHOG_HOST }}
run: |
vercel pull --yes --environment=production --token=$VERCEL_TOKEN
vercel build --prod --token=$VERCEL_TOKEN
vercel deploy --prod --prebuilt --token=$VERCEL_TOKEN \
- --env CLERK_SECRET_KEY=$CLERK_SECRET_KEY
+ --env CLERK_SECRET_KEY=$CLERK_SECRET_KEY \
+ --env NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL \
+ --env NEXT_PUBLIC_WEB_URL=$NEXT_PUBLIC_WEB_URL \
+ --env NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=$NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY \
+ --env NEXT_PUBLIC_COOKIE_DOMAIN=$NEXT_PUBLIC_COOKIE_DOMAIN \
+ --env NEXT_PUBLIC_POSTHOG_KEY=$NEXT_PUBLIC_POSTHOG_KEY \
+ --env NEXT_PUBLIC_POSTHOG_HOST=$NEXT_PUBLIC_POSTHOG_HOST
deploy-admin:
name: Deploy Admin to Vercel
@@ -225,13 +242,21 @@ jobs:
CLERK_SECRET_KEY: ${{ secrets.CLERK_SECRET_KEY }}
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: ${{ secrets.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY }}
NEXT_PUBLIC_COOKIE_DOMAIN: ${{ secrets.NEXT_PUBLIC_COOKIE_DOMAIN }}
+ NEXT_PUBLIC_POSTHOG_KEY: ${{ secrets.NEXT_PUBLIC_POSTHOG_KEY }}
+ NEXT_PUBLIC_POSTHOG_HOST: ${{ secrets.NEXT_PUBLIC_POSTHOG_HOST }}
run: |
vercel pull --yes --environment=production --token=$VERCEL_TOKEN
vercel build --prod --token=$VERCEL_TOKEN
vercel deploy --prod --prebuilt --token=$VERCEL_TOKEN \
--env CLERK_SECRET_KEY=$CLERK_SECRET_KEY \
--env DATABASE_URL=$DATABASE_URL \
- --env DATABASE_URL_UNPOOLED=$DATABASE_URL_UNPOOLED
+ --env DATABASE_URL_UNPOOLED=$DATABASE_URL_UNPOOLED \
+ --env NEXT_PUBLIC_API_URL=$NEXT_PUBLIC_API_URL \
+ --env NEXT_PUBLIC_WEB_URL=$NEXT_PUBLIC_WEB_URL \
+ --env NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=$NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY \
+ --env NEXT_PUBLIC_COOKIE_DOMAIN=$NEXT_PUBLIC_COOKIE_DOMAIN \
+ --env NEXT_PUBLIC_POSTHOG_KEY=$NEXT_PUBLIC_POSTHOG_KEY \
+ --env NEXT_PUBLIC_POSTHOG_HOST=$NEXT_PUBLIC_POSTHOG_HOST
deploy-docs:
name: Deploy Docs to Vercel
@@ -268,7 +293,11 @@ jobs:
NEXT_PUBLIC_API_URL: ${{ secrets.NEXT_PUBLIC_API_URL }}
NEXT_PUBLIC_WEB_URL: ${{ secrets.NEXT_PUBLIC_WEB_URL }}
NEXT_PUBLIC_MARKETING_URL: ${{ secrets.NEXT_PUBLIC_MARKETING_URL }}
+ NEXT_PUBLIC_POSTHOG_KEY: ${{ secrets.NEXT_PUBLIC_POSTHOG_KEY }}
+ NEXT_PUBLIC_POSTHOG_HOST: ${{ secrets.NEXT_PUBLIC_POSTHOG_HOST }}
run: |
vercel pull --yes --environment=production --token=$VERCEL_TOKEN
vercel build --prod --token=$VERCEL_TOKEN
- vercel deploy --prod --prebuilt --token=$VERCEL_TOKEN
+ vercel deploy --prod --prebuilt --token=$VERCEL_TOKEN \
+ --env NEXT_PUBLIC_POSTHOG_KEY=$NEXT_PUBLIC_POSTHOG_KEY \
+ --env NEXT_PUBLIC_POSTHOG_HOST=$NEXT_PUBLIC_POSTHOG_HOST
diff --git a/AGENTS.md b/AGENTS.md
index 6a171f37049..2f72233e765 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -27,6 +27,7 @@ Bun + Turbo monorepo with:
- **Database**: Drizzle ORM + Neon PostgreSQL
- **UI**: React + TailwindCSS v4 + shadcn/ui
- **Code Quality**: Biome (formatting + linting at root)
+- **Next.js**: Version 16 - NEVER create `middleware.ts`. Next.js 16 renamed middleware to `proxy.ts`. Always use `proxy.ts` for request interception.
## Common Commands
diff --git a/apps/admin/next.config.ts b/apps/admin/next.config.ts
index db7abcb7fef..767a9ce0daa 100644
--- a/apps/admin/next.config.ts
+++ b/apps/admin/next.config.ts
@@ -10,6 +10,25 @@ if (process.env.NODE_ENV !== "production") {
const config: NextConfig = {
reactCompiler: true,
typescript: { ignoreBuildErrors: true },
+
+ async rewrites() {
+ return [
+ {
+ source: "/ingest/static/:path*",
+ destination: "https://us-assets.i.posthog.com/static/:path*",
+ },
+ {
+ source: "/ingest/:path*",
+ destination: "https://us.i.posthog.com/:path*",
+ },
+ {
+ source: "/ingest/decide",
+ destination: "https://us.i.posthog.com/decide",
+ },
+ ];
+ },
+
+ skipTrailingSlashRedirect: true,
};
export default config;
diff --git a/apps/admin/package.json b/apps/admin/package.json
index 99ae15f1a3a..87e3813655f 100644
--- a/apps/admin/package.json
+++ b/apps/admin/package.json
@@ -26,6 +26,7 @@
"date-fns": "^4.1.0",
"next": "^16.0.10",
"next-themes": "^0.4.6",
+ "posthog-js": "^1.306.1",
"react": "^19.2.3",
"react-dom": "^19.2.3",
"react-icons": "^5.5.0",
diff --git a/apps/admin/src/app/providers.tsx b/apps/admin/src/app/providers.tsx
index c28c8dc69c0..5b0348e1382 100644
--- a/apps/admin/src/app/providers.tsx
+++ b/apps/admin/src/app/providers.tsx
@@ -3,22 +3,29 @@
import { THEME_STORAGE_KEY } from "@superset/shared/constants";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import { ThemeProvider } from "next-themes";
+import posthog from "posthog-js";
+import { PostHogProvider } from "posthog-js/react";
+
+import { PostHogUserIdentifier } from "@/components/PostHogUserIdentifier";
import { TRPCReactProvider } from "../trpc/react";
export function Providers({ children }: { children: React.ReactNode }) {
return (
-
-
- {children}
-
-
-
+
+
+
+
+ {children}
+
+
+
+
);
}
diff --git a/apps/admin/src/components/PostHogUserIdentifier/PostHogUserIdentifier.tsx b/apps/admin/src/components/PostHogUserIdentifier/PostHogUserIdentifier.tsx
new file mode 100644
index 00000000000..656dce84cb1
--- /dev/null
+++ b/apps/admin/src/components/PostHogUserIdentifier/PostHogUserIdentifier.tsx
@@ -0,0 +1,24 @@
+"use client";
+
+import { useUser } from "@clerk/nextjs";
+import posthog from "posthog-js";
+import { useEffect } from "react";
+
+export function PostHogUserIdentifier() {
+ const { user, isLoaded } = useUser();
+
+ useEffect(() => {
+ if (!isLoaded) return;
+
+ if (user) {
+ posthog.identify(user.id, {
+ email: user.primaryEmailAddress?.emailAddress,
+ name: user.fullName,
+ });
+ } else {
+ posthog.reset();
+ }
+ }, [user, isLoaded]);
+
+ return null;
+}
diff --git a/apps/admin/src/components/PostHogUserIdentifier/index.ts b/apps/admin/src/components/PostHogUserIdentifier/index.ts
new file mode 100644
index 00000000000..9c592fde4de
--- /dev/null
+++ b/apps/admin/src/components/PostHogUserIdentifier/index.ts
@@ -0,0 +1 @@
+export { PostHogUserIdentifier } from "./PostHogUserIdentifier";
diff --git a/apps/admin/src/env.ts b/apps/admin/src/env.ts
index 8c8857439c1..faabb681dbe 100644
--- a/apps/admin/src/env.ts
+++ b/apps/admin/src/env.ts
@@ -21,6 +21,8 @@ export const env = createEnv({
NEXT_PUBLIC_WEB_URL: z.string().url(),
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: z.string(),
NEXT_PUBLIC_COOKIE_DOMAIN: z.string(),
+ NEXT_PUBLIC_POSTHOG_KEY: z.string(),
+ NEXT_PUBLIC_POSTHOG_HOST: z.string().url(),
},
experimental__runtimeEnv: {
@@ -30,6 +32,8 @@ export const env = createEnv({
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY:
process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY,
NEXT_PUBLIC_COOKIE_DOMAIN: process.env.NEXT_PUBLIC_COOKIE_DOMAIN,
+ NEXT_PUBLIC_POSTHOG_KEY: process.env.NEXT_PUBLIC_POSTHOG_KEY,
+ NEXT_PUBLIC_POSTHOG_HOST: process.env.NEXT_PUBLIC_POSTHOG_HOST,
},
skipValidation: !!process.env.SKIP_ENV_VALIDATION,
diff --git a/apps/admin/src/instrumentation-client.ts b/apps/admin/src/instrumentation-client.ts
new file mode 100644
index 00000000000..2e3ac6543b3
--- /dev/null
+++ b/apps/admin/src/instrumentation-client.ts
@@ -0,0 +1,23 @@
+import { POSTHOG_COOKIE_NAME } from "@superset/shared/constants";
+import posthog from "posthog-js";
+
+import { env } from "@/env";
+
+posthog.init(env.NEXT_PUBLIC_POSTHOG_KEY, {
+ api_host: "/ingest",
+ ui_host: "https://us.posthog.com",
+ defaults: "2025-11-30",
+ capture_pageview: "history_change",
+ capture_pageleave: true,
+ capture_exceptions: true,
+ debug: env.NODE_ENV === "development",
+ cross_subdomain_cookie: true,
+ persistence: "cookie",
+ persistence_name: POSTHOG_COOKIE_NAME,
+ loaded: (posthog) => {
+ posthog.register({
+ app_name: "admin",
+ domain: window.location.hostname,
+ });
+ },
+});
diff --git a/apps/desktop/src/renderer/components/PostHogUserIdentifier/PostHogUserIdentifier.tsx b/apps/desktop/src/renderer/components/PostHogUserIdentifier/PostHogUserIdentifier.tsx
new file mode 100644
index 00000000000..b361223dfc4
--- /dev/null
+++ b/apps/desktop/src/renderer/components/PostHogUserIdentifier/PostHogUserIdentifier.tsx
@@ -0,0 +1,23 @@
+import { useEffect } from "react";
+import { trpc } from "renderer/lib/trpc";
+
+import { posthog } from "../../lib/posthog";
+
+export function PostHogUserIdentifier() {
+ const { data: user, isLoading } = trpc.user.me.useQuery();
+
+ useEffect(() => {
+ if (isLoading) return;
+
+ if (user) {
+ posthog.identify(user.id, {
+ email: user.email,
+ name: user.name,
+ });
+ } else {
+ posthog.reset();
+ }
+ }, [user, isLoading]);
+
+ return null;
+}
diff --git a/apps/desktop/src/renderer/components/PostHogUserIdentifier/index.ts b/apps/desktop/src/renderer/components/PostHogUserIdentifier/index.ts
new file mode 100644
index 00000000000..9c592fde4de
--- /dev/null
+++ b/apps/desktop/src/renderer/components/PostHogUserIdentifier/index.ts
@@ -0,0 +1 @@
+export { PostHogUserIdentifier } from "./PostHogUserIdentifier";
diff --git a/apps/desktop/src/renderer/contexts/AppProviders.tsx b/apps/desktop/src/renderer/contexts/AppProviders.tsx
index 0ecd8980969..719536e68d8 100644
--- a/apps/desktop/src/renderer/contexts/AppProviders.tsx
+++ b/apps/desktop/src/renderer/contexts/AppProviders.tsx
@@ -1,4 +1,5 @@
import type React from "react";
+import { PostHogUserIdentifier } from "renderer/components/PostHogUserIdentifier";
import { MonacoProvider } from "./MonacoProvider";
import { PostHogProvider } from "./PostHogProvider";
import { TRPCProvider } from "./TRPCProvider";
@@ -11,6 +12,7 @@ export function AppProviders({ children }: AppProvidersProps) {
return (
+
{children}
diff --git a/apps/desktop/src/renderer/contexts/PostHogProvider.tsx b/apps/desktop/src/renderer/contexts/PostHogProvider.tsx
index 2863b06daf7..ab9e56ef4b5 100644
--- a/apps/desktop/src/renderer/contexts/PostHogProvider.tsx
+++ b/apps/desktop/src/renderer/contexts/PostHogProvider.tsx
@@ -1,64 +1,24 @@
-// TEMPORARILY DISABLED - PostHog bricked the desktop app
-// TODO: Re-enable after fixing the issue
-
-// import posthog from "posthog-js";
-// import { PostHogProvider as PHProvider } from "posthog-js/react";
import type React from "react";
+import { useEffect, useState } from "react";
-// import { useEffect, useState } from "react";
-
-// const POSTHOG_KEY = import.meta.env.NEXT_PUBLIC_POSTHOG_KEY as
-// | string
-// | undefined;
-// const POSTHOG_HOST =
-// (import.meta.env.NEXT_PUBLIC_POSTHOG_HOST as string | undefined) ||
-// "https://us.i.posthog.com";
+import { initPostHog } from "../lib/posthog";
interface PostHogProviderProps {
children: React.ReactNode;
}
export function PostHogProvider({ children }: PostHogProviderProps) {
- // TEMPORARILY DISABLED - PostHog bricked the desktop app
- // const [isInitialized, setIsInitialized] = useState(false);
-
- // useEffect(() => {
- // if (!POSTHOG_KEY) {
- // console.log("[posthog] No PostHog key configured, skipping init");
- // setIsInitialized(true);
- // return;
- // }
-
- // posthog.init(POSTHOG_KEY, {
- // api_host: POSTHOG_HOST,
- // // Electron apps don't have traditional page views
- // capture_pageview: false,
- // // Disable session recording for now
- // disable_session_recording: true,
- // // Persist across sessions
- // persistence: "localStorage",
- // // Load feature flags on init
- // bootstrap: {
- // featureFlags: {},
- // },
- // });
-
- // setIsInitialized(true);
- // console.log("[posthog] Initialized");
- // }, []);
-
- // // Don't render children until PostHog is initialized (or skipped)
- // if (!isInitialized) {
- // return null;
- // }
+ const [isInitialized, setIsInitialized] = useState(false);
- // // If no PostHog key, just render children without the provider
- // if (!POSTHOG_KEY) {
- // return <>{children}>;
- // }
+ useEffect(() => {
+ initPostHog();
+ setIsInitialized(true);
+ }, []);
- // return {children};
+ // Don't render children until PostHog is initialized
+ if (!isInitialized) {
+ return null;
+ }
- // Just render children without PostHog
return <>{children}>;
}
diff --git a/apps/desktop/src/renderer/lib/posthog.ts b/apps/desktop/src/renderer/lib/posthog.ts
new file mode 100644
index 00000000000..a1b604786fe
--- /dev/null
+++ b/apps/desktop/src/renderer/lib/posthog.ts
@@ -0,0 +1,34 @@
+import posthog from "posthog-js/dist/module.full.no-external";
+
+const POSTHOG_KEY = import.meta.env.NEXT_PUBLIC_POSTHOG_KEY as
+ | string
+ | undefined;
+
+export function initPostHog() {
+ if (!POSTHOG_KEY) {
+ console.log("[posthog] No key configured, skipping");
+ return;
+ }
+
+ posthog.init(POSTHOG_KEY, {
+ api_host: "https://us.i.posthog.com",
+ ui_host: "https://us.posthog.com",
+ defaults: "2025-11-30",
+ capture_pageview: false,
+ capture_pageleave: false,
+ capture_exceptions: true,
+ person_profiles: "identified_only",
+ persistence: "localStorage",
+ debug: import.meta.env.DEV,
+ loaded: (ph) => {
+ ph.register({
+ app_name: "desktop",
+ platform: window.navigator.platform,
+ });
+ },
+ });
+
+ console.log("[posthog] Initialized");
+}
+
+export { posthog };
diff --git a/apps/docs/next.config.ts b/apps/docs/next.config.ts
index dfe5e3d6b7e..08bb3e65b68 100644
--- a/apps/docs/next.config.ts
+++ b/apps/docs/next.config.ts
@@ -1,12 +1,45 @@
+import { join } from "node:path";
+import { config as dotenvConfig } from "dotenv";
import type { NextConfig } from "next";
import nextra from "nextra";
+// Load .env from monorepo root during development
+if (process.env.NODE_ENV !== "production") {
+ dotenvConfig({ path: join(process.cwd(), "../../.env"), override: true });
+}
+
const withNextra = nextra({
defaultShowCopyCode: true,
});
const nextConfig: NextConfig = {
reactStrictMode: true,
+
+ /** Turbopack MDX resolution for nextra */
+ turbopack: {
+ resolveAlias: {
+ "next-mdx-import-source-file": "./mdx-components.tsx",
+ },
+ },
+
+ async rewrites() {
+ return [
+ {
+ source: "/ingest/static/:path*",
+ destination: "https://us-assets.i.posthog.com/static/:path*",
+ },
+ {
+ source: "/ingest/:path*",
+ destination: "https://us.i.posthog.com/:path*",
+ },
+ {
+ source: "/ingest/decide",
+ destination: "https://us.i.posthog.com/decide",
+ },
+ ];
+ },
+
+ skipTrailingSlashRedirect: true,
};
export default withNextra(nextConfig);
diff --git a/apps/docs/package.json b/apps/docs/package.json
index b546c221168..e7a1f172426 100644
--- a/apps/docs/package.json
+++ b/apps/docs/package.json
@@ -12,16 +12,21 @@
"dependencies": {
"@react-three/drei": "^10.7.6",
"@react-three/fiber": "^9.4.0",
+ "@superset/shared": "workspace:*",
"@superset/ui": "workspace:*",
+ "@t3-oss/env-nextjs": "^0.13.8",
+ "dotenv": "^17.2.3",
"framer-motion": "^12.23.24",
"geist": "^1.5.1",
"next": "^16.0.10",
"nextra": "^4.6.1",
"nextra-theme-blog": "^4.6.1",
"nextra-theme-docs": "^4.6.1",
+ "posthog-js": "^1.306.1",
"react": "^19.2.3",
"react-dom": "^19.2.3",
- "three": "^0.181.2"
+ "three": "^0.181.2",
+ "zod": "^4.1.13"
},
"devDependencies": {
"@superset/typescript": "workspace:*",
diff --git a/apps/docs/src/app/layout.tsx b/apps/docs/src/app/layout.tsx
index 856992ee7cc..474ec300453 100644
--- a/apps/docs/src/app/layout.tsx
+++ b/apps/docs/src/app/layout.tsx
@@ -4,6 +4,8 @@ import { Footer, Layout, Navbar } from "nextra-theme-docs";
import type * as React from "react";
import "nextra-theme-docs/style.css";
+import { Providers } from "./providers";
+
export const metadata = {
title: "Superset Docs",
description: "Superset Documentation",
@@ -31,15 +33,17 @@ export default async function DocsLayout({
return (
-
- {children}
-
+
+
+ {children}
+
+
);
diff --git a/apps/docs/src/app/providers.tsx b/apps/docs/src/app/providers.tsx
new file mode 100644
index 00000000000..2a69d18923b
--- /dev/null
+++ b/apps/docs/src/app/providers.tsx
@@ -0,0 +1,8 @@
+"use client";
+
+import posthog from "posthog-js";
+import { PostHogProvider } from "posthog-js/react";
+
+export function Providers({ children }: { children: React.ReactNode }) {
+ return {children};
+}
diff --git a/apps/docs/src/env.ts b/apps/docs/src/env.ts
new file mode 100644
index 00000000000..0e1096f251c
--- /dev/null
+++ b/apps/docs/src/env.ts
@@ -0,0 +1,28 @@
+import { createEnv } from "@t3-oss/env-nextjs";
+import { vercel } from "@t3-oss/env-nextjs/presets-zod";
+import { z } from "zod";
+
+export const env = createEnv({
+ extends: [vercel()],
+ shared: {
+ NODE_ENV: z
+ .enum(["development", "production", "test"])
+ .default("development"),
+ },
+
+ server: {},
+
+ client: {
+ NEXT_PUBLIC_POSTHOG_KEY: z.string(),
+ NEXT_PUBLIC_POSTHOG_HOST: z.string().url(),
+ },
+
+ experimental__runtimeEnv: {
+ NODE_ENV: process.env.NODE_ENV,
+ NEXT_PUBLIC_POSTHOG_KEY: process.env.NEXT_PUBLIC_POSTHOG_KEY,
+ NEXT_PUBLIC_POSTHOG_HOST: process.env.NEXT_PUBLIC_POSTHOG_HOST,
+ },
+
+ skipValidation: !!process.env.SKIP_ENV_VALIDATION,
+ emptyStringAsUndefined: true,
+});
diff --git a/apps/docs/src/instrumentation-client.ts b/apps/docs/src/instrumentation-client.ts
new file mode 100644
index 00000000000..bbd347f7536
--- /dev/null
+++ b/apps/docs/src/instrumentation-client.ts
@@ -0,0 +1,23 @@
+import { POSTHOG_COOKIE_NAME } from "@superset/shared/constants";
+import posthog from "posthog-js";
+
+import { env } from "@/env";
+
+posthog.init(env.NEXT_PUBLIC_POSTHOG_KEY, {
+ api_host: "/ingest",
+ ui_host: "https://us.posthog.com",
+ defaults: "2025-11-30",
+ capture_pageview: "history_change",
+ capture_pageleave: true,
+ capture_exceptions: true,
+ debug: env.NODE_ENV === "development",
+ cross_subdomain_cookie: true,
+ persistence: "cookie",
+ persistence_name: POSTHOG_COOKIE_NAME,
+ loaded: (posthog) => {
+ posthog.register({
+ app_name: "docs",
+ domain: window.location.hostname,
+ });
+ },
+});
diff --git a/apps/marketing/next.config.ts b/apps/marketing/next.config.ts
index c9b9e812a6c..034b7731376 100644
--- a/apps/marketing/next.config.ts
+++ b/apps/marketing/next.config.ts
@@ -11,6 +11,25 @@ const config: NextConfig = {
reactStrictMode: true,
reactCompiler: true,
typescript: { ignoreBuildErrors: true },
+
+ async rewrites() {
+ return [
+ {
+ source: "/ingest/static/:path*",
+ destination: "https://us-assets.i.posthog.com/static/:path*",
+ },
+ {
+ source: "/ingest/:path*",
+ destination: "https://us.i.posthog.com/:path*",
+ },
+ {
+ source: "/ingest/decide",
+ destination: "https://us.i.posthog.com/decide",
+ },
+ ];
+ },
+
+ skipTrailingSlashRedirect: true,
};
export default config;
diff --git a/apps/marketing/package.json b/apps/marketing/package.json
index 6c0656c0c35..f3371da3f55 100644
--- a/apps/marketing/package.json
+++ b/apps/marketing/package.json
@@ -22,6 +22,7 @@
"lucide-react": "^0.560.0",
"next": "^16.0.10",
"next-themes": "^0.4.6",
+ "posthog-js": "^1.306.1",
"react": "^19.2.3",
"react-dom": "^19.2.3",
"react-fast-marquee": "^1.6.5",
diff --git a/apps/marketing/src/app/layout.tsx b/apps/marketing/src/app/layout.tsx
index e6c95fbc026..a17546bab03 100644
--- a/apps/marketing/src/app/layout.tsx
+++ b/apps/marketing/src/app/layout.tsx
@@ -1,16 +1,16 @@
import { ClerkProvider } from "@clerk/nextjs";
-import { THEME_STORAGE_KEY } from "@superset/shared/constants";
import type { Metadata } from "next";
import { IBM_Plex_Mono, Inter } from "next/font/google";
import Script from "next/script";
-import { ThemeProvider } from "next-themes";
+import { CookieConsent } from "@/components/CookieConsent";
import { env } from "@/env";
import { CTAButtons } from "./components/CTAButtons";
import { Footer } from "./components/Footer";
import { Header } from "./components/Header";
import "./globals.css";
+import { Providers } from "./providers";
const ibmPlexMono = IBM_Plex_Mono({
weight: ["300", "400", "500"],
@@ -49,17 +49,12 @@ export default function RootLayout({
/>
-
+
} />
{children}
-
+
+