diff --git a/docs/.env.example b/docs/.env.example
index 6a3eff580..f239db7a1 100644
--- a/docs/.env.example
+++ b/docs/.env.example
@@ -5,3 +5,7 @@
DOCSEARCH_APP_ID=
DOCSEARCH_INDEX_NAME=
DOCSEARCH_API_KEY=
+
+# PostHog public project token (see shared/components/Analytics.astro).
+# Only used in Vercel production builds; leave empty locally.
+POSTHOG_KEY=
diff --git a/docs/layouts/DocsLayout.astro b/docs/layouts/DocsLayout.astro
index 15e31c848..431138936 100644
--- a/docs/layouts/DocsLayout.astro
+++ b/docs/layouts/DocsLayout.astro
@@ -3,6 +3,7 @@
// Standalone layout with its own
; does NOT use BaseLayout.astro.
// Development uses the unminified assets; production additionally emits the SEO metadata.
import { site } from '@shared/lib/site';
+import Analytics from '@shared/components/Analytics.astro';
import { isExternal } from '@shared/lib/url';
import libs from '@tabler/core/libs.json';
import docs from '@data/docs.json';
@@ -308,6 +309,8 @@ const relatedPages = await Promise.all(
+
+
diff --git a/docs/vercel.json b/docs/vercel.json
index e4f3c11cb..76001c97a 100644
--- a/docs/vercel.json
+++ b/docs/vercel.json
@@ -38,5 +38,9 @@
{ "key": "Vary", "value": "Accept" }
]
}
+ ],
+ "rewrites": [
+ { "source": "/eat/static/:path(.*)", "destination": "https://eu-assets.i.posthog.com/static/:path*" },
+ { "source": "/eat/:path(.*)", "destination": "https://eu.i.posthog.com/:path*" }
]
}
diff --git a/preview/vercel.json b/preview/vercel.json
index 4c9042c2a..cad7d3303 100644
--- a/preview/vercel.json
+++ b/preview/vercel.json
@@ -5,6 +5,8 @@
"status": 308,
"headers": { "Location": "/marketing.html" }
},
+ { "src": "/eat/static/(.*)", "dest": "https://eu-assets.i.posthog.com/static/$1" },
+ { "src": "/eat/(.*)", "dest": "https://eu.i.posthog.com/$1" },
{ "handle": "filesystem" },
{
"src": "/(.*)",
diff --git a/shared/components/Analytics.astro b/shared/components/Analytics.astro
new file mode 100644
index 000000000..fa28b5056
--- /dev/null
+++ b/shared/components/Analytics.astro
@@ -0,0 +1,25 @@
+---
+// PostHog web analytics for the public sites. The snippet is emitted only in
+// Vercel production builds (VERCEL_ENV is unset locally and "preview" on branch
+// deployments), so the dev server, local builds and branch previews ship no
+// tracking code at all. A runtime hostname check is kept as a second guard.
+// The public project token comes from the POSTHOG_KEY env var (Vercel project
+// settings, Production only); without it nothing is emitted. Requests go through
+// the same-origin `/eat/*` proxy (see vercel.json in docs/ and preview/), which
+// rewrites to eu.i.posthog.com.
+//
+// The loader is PostHog's minified stub; it is assembled as a string because the
+// Astro prettier parser cannot handle it inline.
+const isProduction = process.env.VERCEL_ENV === 'production'
+const key = process.env.POSTHOG_KEY
+const hosts = ['docs.tabler.io', 'preview.tabler.io', 'dev.tabler.io']
+
+const loader =
+ '!function(t,e){var o,n,p,r;e.__SV||(window.posthog=e,e._i=[],e.init=function(i,s,a){function g(t,e){var o=e.split(".");2==o.length&&(t=t[o[0]],e=o[1]),t[e]=function(){t.push([e].concat(Array.prototype.slice.call(arguments,0)))}}(p=t.createElement("script")).type="text/javascript",p.crossOrigin="anonymous",p.async=!0,p.src=s.api_host.replace(".i.posthog.com","-assets.i.posthog.com")+"/static/array.js",(r=t.getElementsByTagName("script")[0]).parentNode.insertBefore(p,r);var u=e;for(void 0!==a?u=e[a]=[]:a="posthog",u.people=u.people||[],u.toString=function(t){var e="posthog";return"posthog"!==a&&(e+="."+a),t||(e+=" (stub)"),e},u.people.toString=function(){return u.toString(1)+".people (stub)"},o="init capture register register_once register_for_session unregister unregister_for_session getFeatureFlag getFeatureFlagPayload isFeatureEnabled reloadFeatureFlags updateEarlyAccessFeatureEnrollment getEarlyAccessFeatures on onFeatureFlags onSessionId getSurveys getActiveMatchingSurveys renderSurvey canRenderSurvey getNextSurveyStep identify setPersonProperties group resetGroups setPersonPropertiesForFlags resetPersonPropertiesForFlags setGroupPropertiesForFlags resetGroupPropertiesForFlags reset get_distinct_id getGroups get_session_id get_session_replay_url alias set_config startSessionRecording stopSessionRecording sessionRecordingStarted captureException loadToolbar get_property getSessionProperty createPersonProfile opt_in_capturing opt_out_capturing has_opted_in_capturing has_opted_out_capturing clear_opt_in_out_capturing debug".split(" "),n=0;n}
diff --git a/shared/layouts/BaseLayout.astro b/shared/layouts/BaseLayout.astro
index 5f2582d79..586c3e671 100644
--- a/shared/layouts/BaseLayout.astro
+++ b/shared/layouts/BaseLayout.astro
@@ -3,6 +3,7 @@ import ThemeSettings from '@shared/components/demo/ThemeSettings.astro'
import { site } from '@shared/lib/site'
import PageModals from '@shared/components/PageModals.astro'
import PageScripts from '@shared/components/PageScripts.astro'
+import Analytics from '@shared/components/Analytics.astro'
import libs from '@tabler/core/libs.json'
import { themeDefaults, themePresets, sidebarOnlyKeys, navbarOnlyKeys, themeDataAttributes, type ThemeKey } from '@shared/lib/theme-config'
@@ -104,6 +105,8 @@ const libJsFiles = (head: boolean) => pageLibEntries.filter(([, lib]) => Boolean
)
}
+
+
diff --git a/turbo.json b/turbo.json
index ee0467ace..20500967e 100644
--- a/turbo.json
+++ b/turbo.json
@@ -1,6 +1,6 @@
{
"$schema": "https://turbo.build/schema.json",
- "globalEnv": ["NODE_ENV", "VERCEL_ENV", "NEXT_PUBLIC_POSTHOG_KEY", "NEXT_PUBLIC_POSTHOG_HOST", "DOCSEARCH_API_KEY", "DOCSEARCH_APP_ID", "DOCSEARCH_INDEX_NAME", "GOOGLE_MAPS_KEY", "MAPBOX_KEY"],
+ "globalEnv": ["NODE_ENV", "VERCEL_ENV", "POSTHOG_KEY", "DOCSEARCH_API_KEY", "DOCSEARCH_APP_ID", "DOCSEARCH_INDEX_NAME", "GOOGLE_MAPS_KEY", "MAPBOX_KEY"],
"tasks": {
"build": {
"dependsOn": ["^build"],