diff --git a/env.example b/env.example
index fc7d984..3cbc0c9 100644
--- a/env.example
+++ b/env.example
@@ -132,6 +132,12 @@ NEXT_PUBLIC_SELINE_TOKEN=""
# -----------------------------------------------------------------------------
NEXT_PUBLIC_DATAFAST_WEBSITE_ID=""
NEXT_PUBLIC_DATAFAST_DOMAIN=""
+# -----------------------------------------------------------------------------
+# PostHog Analytics (https://posthog.com)
+# https://mksaas.com/docs/analytics#posthog
+# -----------------------------------------------------------------------------
+NEXT_PUBLIC_POSTHOG_KEY=""
+NEXT_PUBLIC_POSTHOG_HOST=""
# -----------------------------------------------------------------------------
diff --git a/package.json b/package.json
index c7dd5fb..3fb0334 100644
--- a/package.json
+++ b/package.json
@@ -113,6 +113,7 @@
"next-themes": "^0.4.4",
"nuqs": "^2.5.1",
"postgres": "^3.4.5",
+ "posthog-js": "^1.261.7",
"radix-ui": "^1.4.2",
"react": "^19.0.0",
"react-day-picker": "8.10.1",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 8d90938..aa81f24 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -272,6 +272,9 @@ importers:
postgres:
specifier: ^3.4.5
version: 3.4.5
+ posthog-js:
+ specifier: ^1.261.7
+ version: 1.261.7
radix-ui:
specifier: ^1.4.2
version: 1.4.2(@types/react-dom@19.0.3(@types/react@19.0.9))(@types/react@19.0.9)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
@@ -1926,6 +1929,9 @@ packages:
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
engines: {node: '>=14'}
+ '@posthog/core@1.0.2':
+ resolution: {integrity: sha512-hWk3rUtJl2crQK0WNmwg13n82hnTwB99BT99/XI5gZSvIlYZ1TPmMZE8H2dhJJ98J/rm9vYJ/UXNzw3RV5HTpQ==}
+
'@radix-ui/number@1.1.0':
resolution: {integrity: sha512-V3gRzhVNU1ldS5XhAPTom1fOIo4ccrjjJgmE+LI2h/WaFpHmx0MQApT+KZHnx8abG6Avtfcz4WoEciMnpFT3HQ==}
@@ -4025,6 +4031,9 @@ packages:
resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==}
engines: {node: '>=18'}
+ core-js@3.45.1:
+ resolution: {integrity: sha512-L4NPsJlCfZsPeXukyzHFlg/i7IIVwHSItR0wg0FLNqYClJ4MQYTYLbC7EkjKYRLZF2iof2MUgN0EGy7MdQFChg==}
+
cors@2.8.5:
resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==}
engines: {node: '>= 0.10'}
@@ -4503,6 +4512,9 @@ packages:
picomatch:
optional: true
+ fflate@0.4.8:
+ resolution: {integrity: sha512-FJqqoDBR00Mdj9ppamLa/Y7vxm+PRmNWA67N846RvsoYVMKB4q3y/de5PA7gUmRMYK/8CMz2GDZQmCRN1wBcWA==}
+
file-entry-cache@8.0.0:
resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
engines: {node: '>=16.0.0'}
@@ -5627,6 +5639,20 @@ packages:
resolution: {integrity: sha512-cDWgoah1Gez9rN3H4165peY9qfpEo+SA61oQv65O3cRUE1pOEoJWwddwcqKE8XZYjbblOJlYDlLV4h67HrEVDg==}
engines: {node: '>=12'}
+ posthog-js@1.261.7:
+ resolution: {integrity: sha512-Fjpbz6VfIMsEbKIN/UyTWhU1DGgVIngqoRjPGRolemIMOVzTfI77OZq8WwiBhMug+rU+wNhGCQhC41qRlR5CxA==}
+ peerDependencies:
+ '@rrweb/types': 2.0.0-alpha.17
+ rrweb-snapshot: 2.0.0-alpha.17
+ peerDependenciesMeta:
+ '@rrweb/types':
+ optional: true
+ rrweb-snapshot:
+ optional: true
+
+ preact@10.27.1:
+ resolution: {integrity: sha512-V79raXEWch/rbqoNc7nT9E4ep7lu+mI3+sBmfRD4i1M73R3WLYcCtdI0ibxGVf4eQL8ZIz2nFacqEC+rmnOORQ==}
+
prelude-ls@1.2.1:
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
engines: {node: '>= 0.8.0'}
@@ -6355,6 +6381,9 @@ packages:
web-namespaces@2.0.1:
resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==}
+ web-vitals@4.2.4:
+ resolution: {integrity: sha512-r4DIlprAGwJ7YM11VZp4R884m0Vmgr6EAKe3P+kO0PPj3Unqyvv59rczf6UiGcb9Z8QxZVcqKNwv/g0WNdWwsw==}
+
which@2.0.2:
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
engines: {node: '>= 8'}
@@ -7593,6 +7622,8 @@ snapshots:
'@pkgjs/parseargs@0.11.0':
optional: true
+ '@posthog/core@1.0.2': {}
+
'@radix-ui/number@1.1.0': {}
'@radix-ui/number@1.1.1': {}
@@ -9808,6 +9839,8 @@ snapshots:
cookie@1.0.2: {}
+ core-js@3.45.1: {}
+
cors@2.8.5:
dependencies:
object-assign: 4.1.1
@@ -10335,6 +10368,8 @@ snapshots:
optionalDependencies:
picomatch: 4.0.2
+ fflate@0.4.8: {}
+
file-entry-cache@8.0.0:
dependencies:
flat-cache: 4.0.1
@@ -11784,6 +11819,16 @@ snapshots:
postgres@3.4.5: {}
+ posthog-js@1.261.7:
+ dependencies:
+ '@posthog/core': 1.0.2
+ core-js: 3.45.1
+ fflate: 0.4.8
+ preact: 10.27.1
+ web-vitals: 4.2.4
+
+ preact@10.27.1: {}
+
prelude-ls@1.2.1: {}
prettier@3.4.2: {}
@@ -12744,6 +12789,8 @@ snapshots:
web-namespaces@2.0.1: {}
+ web-vitals@4.2.4: {}
+
which@2.0.2:
dependencies:
isexe: 2.0.0
diff --git a/src/analytics/posthog-analytics.tsx b/src/analytics/posthog-analytics.tsx
new file mode 100644
index 0000000..3808723
--- /dev/null
+++ b/src/analytics/posthog-analytics.tsx
@@ -0,0 +1,35 @@
+'use client';
+
+import posthog from 'posthog-js';
+import { PostHogProvider as PHProvider } from 'posthog-js/react';
+import { useEffect } from 'react';
+
+/**
+ * PostHog Analytics
+ *
+ * https://posthog.com
+ * https://posthog.com/docs/libraries/next-js?tab=PostHog+provider
+ * https://mksaas.com/docs/analytics#posthog
+ */
+export function PostHogProvider({ children }: { children: React.ReactNode }) {
+ const posthogKey = process.env.NEXT_PUBLIC_POSTHOG_KEY;
+ const posthogHost = process.env.NEXT_PUBLIC_POSTHOG_HOST;
+ const isProduction = process.env.NODE_ENV === 'production';
+ const isPostHogEnabled = posthogKey && posthogHost && isProduction;
+
+ useEffect(() => {
+ if (isPostHogEnabled) {
+ posthog.init(posthogKey, {
+ api_host: posthogHost,
+ defaults: '2025-05-24',
+ });
+ }
+ }, [isPostHogEnabled, posthogKey, posthogHost]);
+
+ // If PostHog is not enabled, just return children without the provider
+ if (!isPostHogEnabled) {
+ return <>{children}>;
+ }
+
+ return {children};
+}
diff --git a/src/app/[locale]/providers.tsx b/src/app/[locale]/providers.tsx
index c3e89a9..a6c8ebf 100644
--- a/src/app/[locale]/providers.tsx
+++ b/src/app/[locale]/providers.tsx
@@ -1,5 +1,6 @@
'use client';
+import { PostHogProvider } from '@/analytics/posthog-analytics';
import { ActiveThemeProvider } from '@/components/layout/active-theme-provider';
import { QueryProvider } from '@/components/providers/query-provider';
import { TooltipProvider } from '@/components/ui/tooltip';
@@ -20,12 +21,12 @@ interface ProvidersProps {
*
* This component is used to wrap the app in the providers.
*
+ * - PostHogProvider: Provides the PostHog analytics to the app.
+ * - QueryProvider: Provides the query client to the app.
* - ThemeProvider: Provides the theme to the app.
* - ActiveThemeProvider: Provides the active theme to the app.
* - RootProvider: Provides the root provider for Fumadocs UI.
* - TooltipProvider: Provides the tooltip to the app.
- * - PaymentProvider: Provides the payment state to the app.
- * - CreditsProvider: Provides the credits state to the app.
*/
export function Providers({ children, locale }: ProvidersProps) {
const theme = useTheme();
@@ -53,19 +54,24 @@ export function Providers({ children, locale }: ProvidersProps) {
};
return (
-
-
-
-
- {children}
-
-
-
-
+
+
+
+
+
+ {children}
+
+
+
+
+
);
}