diff --git a/env.example b/env.example
index 4fcd9c3..0e07d31 100644
--- a/env.example
+++ b/env.example
@@ -67,3 +67,27 @@ NEXT_PUBLIC_STRIPE_PRICE_PRO_MONTHLY=""
NEXT_PUBLIC_STRIPE_PRICE_PRO_YEARLY=""
# Lifetime plan - one-time payment
NEXT_PUBLIC_STRIPE_PRICE_LIFETIME=""
+
+# -----------------------------------------------------------------------------
+# Analytics
+# -----------------------------------------------------------------------------
+# Google Analytics (https://analytics.google.com)
+# -----------------------------------------------------------------------------
+NEXT_PUBLIC_GOOGLE_ANALYTICS_ID=""
+# -----------------------------------------------------------------------------
+# Umami Analytics (https://umami.is)
+# -----------------------------------------------------------------------------
+NEXT_PUBLIC_UMAMI_WEBSITE_ID=""
+# -----------------------------------------------------------------------------
+# OpenPanel Analytics (https://openpanel.dev)
+# -----------------------------------------------------------------------------
+NEXT_PUBLIC_OPENPANEL_CLIENT_ID=""
+# -----------------------------------------------------------------------------
+# Plausible Analytics (https://plausible.io)
+# -----------------------------------------------------------------------------
+NEXT_PUBLIC_PLAUSIBLE_DOMAIN=""
+# -----------------------------------------------------------------------------
+# DataFast Analytics (https://datafa.st)
+# -----------------------------------------------------------------------------
+NEXT_PUBLIC_DATAFAST_ANALYTICS_ID=""
+NEXT_PUBLIC_DATAFAST_ANALYTICS_DOMAIN=""
diff --git a/package.json b/package.json
index 99f4249..b0e87c2 100644
--- a/package.json
+++ b/package.json
@@ -28,6 +28,8 @@
"@fumadocs/content-collections": "^1.1.8",
"@hookform/resolvers": "^4.1.0",
"@neondatabase/serverless": "^0.10.4",
+ "@next/third-parties": "^15.3.0",
+ "@openpanel/nextjs": "^1.0.7",
"@orama/orama": "^3.1.4",
"@orama/tokenizers": "^3.1.4",
"@radix-ui/react-accordion": "^1.2.3",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index c3a80bf..45bb0c7 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -47,6 +47,12 @@ importers:
'@neondatabase/serverless':
specifier: ^0.10.4
version: 0.10.4
+ '@next/third-parties':
+ specifier: ^15.3.0
+ version: 15.3.0(next@15.2.1(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)
+ '@openpanel/nextjs':
+ specifier: ^1.0.7
+ version: 1.0.7(next@15.2.1(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
'@orama/orama':
specifier: ^3.1.4
version: 3.1.4
@@ -1809,6 +1815,12 @@ packages:
cpu: [x64]
os: [win32]
+ '@next/third-parties@15.3.0':
+ resolution: {integrity: sha512-fHgeIG5Pf/kW2Yi3man7r4y2f3uYA2sKIA9ylBvD/q9kg8sNWODBm4pYRatdIGosNkHZjVoNUlK85AQ0SP3Rkw==}
+ peerDependencies:
+ next: ^13.0.0 || ^14.0.0 || ^15.0.0
+ react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0
+
'@noble/ciphers@0.6.0':
resolution: {integrity: sha512-mIbq/R9QXk5/cTfESb1OKtyFnk7oc1Om/8onA1158K9/OZUQFDEVy55jVTato+xmp3XX6F6Qh0zz0Nc1AxAlRQ==}
@@ -1819,6 +1831,19 @@ packages:
'@one-ini/wasm@0.1.1':
resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==}
+ '@openpanel/nextjs@1.0.7':
+ resolution: {integrity: sha512-XygTPiVlxUkcnZ3DYYwnWiogDgCL1hFK5G8nxokdrFKKuM0XQ+9mFElW/+dKgnVfmV2QiP1aAscudHAlRSUQhg==}
+ peerDependencies:
+ next: ^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+ react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
+
+ '@openpanel/sdk@1.0.0':
+ resolution: {integrity: sha512-FNmmfjdXoC/VHEjA+WkrQ4lyM5lxEmV7xDd57uj4E+lIS0sU3DLG2mV/dpS8AscnZbUvuMn3kPhiLCqYzuv/gg==}
+
+ '@openpanel/web@1.0.1':
+ resolution: {integrity: sha512-cVZ7Kr9SicczJ/RDIfEtZs8+1iGDzwkabVA/j3NqSl8VSucsC8m1+LVbjmCDzCJNnK4yVn6tEcc9PJRi2rtllw==}
+
'@opentelemetry/api@1.9.0':
resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==}
engines: {node: '>=8.0.0'}
@@ -5265,6 +5290,9 @@ packages:
text-table@0.2.0:
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
+ third-party-capital@1.0.20:
+ resolution: {integrity: sha512-oB7yIimd8SuGptespDAZnNkzIz+NWaJCu2RMsbs4Wmp9zSDUM8Nhi3s2OOcqYuv3mN4hitXc8DVx+LyUmbUDiA==}
+
throttleit@2.1.0:
resolution: {integrity: sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==}
engines: {node: '>=18'}
@@ -6963,12 +6991,31 @@ snapshots:
'@next/swc-win32-x64-msvc@15.2.1':
optional: true
+ '@next/third-parties@15.3.0(next@15.2.1(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react@19.0.0)':
+ dependencies:
+ next: 15.2.1(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ react: 19.0.0
+ third-party-capital: 1.0.20
+
'@noble/ciphers@0.6.0': {}
'@noble/hashes@1.7.1': {}
'@one-ini/wasm@0.1.1': {}
+ '@openpanel/nextjs@1.0.7(next@15.2.1(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0))(react-dom@19.0.0(react@19.0.0))(react@19.0.0)':
+ dependencies:
+ '@openpanel/web': 1.0.1
+ next: 15.2.1(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@19.0.0(react@19.0.0))(react@19.0.0)
+ react: 19.0.0
+ react-dom: 19.0.0(react@19.0.0)
+
+ '@openpanel/sdk@1.0.0': {}
+
+ '@openpanel/web@1.0.1':
+ dependencies:
+ '@openpanel/sdk': 1.0.0
+
'@opentelemetry/api@1.9.0': {}
'@orama/orama@3.1.4': {}
@@ -11112,6 +11159,8 @@ snapshots:
text-table@0.2.0: {}
+ third-party-capital@1.0.20: {}
+
throttleit@2.1.0: {}
tiny-invariant@1.3.3: {}
diff --git a/src/analytics/analytics.tsx b/src/analytics/analytics.tsx
new file mode 100644
index 0000000..c87a27b
--- /dev/null
+++ b/src/analytics/analytics.tsx
@@ -0,0 +1,36 @@
+import GoogleAnalytics from "./google-analytics";
+import { UmamiAnalytics } from "./umami-analytics";
+import { PlausibleAnalytics } from "./plausible-analytics";
+import DataFastAnalytics from "./data-fast-analytics";
+import OpenPanelAnalytics from "./open-panel-analytics";
+
+/**
+ * Analytics Components all in one
+ *
+ * 1. all the analytics components only work in production
+ * 2. only work if the environment variable for the analytics is set
+ */
+export function Analytics() {
+ if (process.env.NODE_ENV !== "production") {
+ return null;
+ }
+
+ return (
+ <>
+ {/* google analytics */}
+
+
+ {/* umami analytics */}
+
+
+ {/* plausible analytics */}
+
+
+ {/* datafast analytics */}
+
+
+ {/* openpanel analytics */}
+
+ >
+ );
+}
diff --git a/src/analytics/data-fast-analytics.tsx b/src/analytics/data-fast-analytics.tsx
new file mode 100644
index 0000000..2faff5e
--- /dev/null
+++ b/src/analytics/data-fast-analytics.tsx
@@ -0,0 +1,35 @@
+"use client";
+
+import Script from "next/script";
+
+/**
+ * DataFast Analytics
+ *
+ * https://datafa.st
+ */
+export default function DataFastAnalytics() {
+ if (process.env.NODE_ENV !== "production") {
+ return null;
+ }
+
+ const domain = process.env.NEXT_PUBLIC_DATAFAST_DOMAIN;
+ if (!domain) {
+ return null;
+ }
+
+ const websiteId = process.env.NEXT_PUBLIC_DATAFAST_WEBSITE_ID;
+ if (!websiteId) {
+ return null;
+ }
+
+ return (
+ <>
+
+ >
+ );
+}
diff --git a/src/analytics/google-analytics.tsx b/src/analytics/google-analytics.tsx
new file mode 100644
index 0000000..941258f
--- /dev/null
+++ b/src/analytics/google-analytics.tsx
@@ -0,0 +1,22 @@
+"use client";
+
+import { GoogleAnalytics as NextGoogleAnalytics } from "@next/third-parties/google";
+
+/**
+ * Google Analytics
+ *
+ * https://analytics.google.com
+ * https://nextjs.org/docs/app/building-your-application/optimizing/third-party-libraries#google-analytics
+ */
+export default function GoogleAnalytics() {
+ if (process.env.NODE_ENV !== "production") {
+ return null;
+ }
+
+ const analyticsId = process.env.NEXT_PUBLIC_GOOGLE_ANALYTICS_ID;
+ if (!analyticsId) {
+ return null;
+ }
+
+ return ;
+}
diff --git a/src/analytics/open-panel-analytics.tsx b/src/analytics/open-panel-analytics.tsx
new file mode 100644
index 0000000..d83f9fe
--- /dev/null
+++ b/src/analytics/open-panel-analytics.tsx
@@ -0,0 +1,26 @@
+import { OpenPanelComponent } from "@openpanel/nextjs";
+
+/**
+ * OpenPanel Analytics (https://openpanel.dev)
+ *
+ * https://docs.openpanel.dev/docs/sdks/nextjs#options
+ */
+export default function OpenPanelAnalytics() {
+ if (process.env.NODE_ENV !== "production") {
+ return null;
+ }
+
+ const clientId = process.env.NEXT_PUBLIC_OPENPANEL_CLIENT_ID;
+ if (!clientId) {
+ return null;
+ }
+
+ return (
+
+ );
+}
diff --git a/src/analytics/plausible-analytics.tsx b/src/analytics/plausible-analytics.tsx
new file mode 100644
index 0000000..ce679d3
--- /dev/null
+++ b/src/analytics/plausible-analytics.tsx
@@ -0,0 +1,28 @@
+"use client";
+
+import Script from "next/script";
+
+/**
+ * Plausible Analytics
+ *
+ * https://plausible.io
+ */
+export function PlausibleAnalytics() {
+ if (process.env.NODE_ENV !== "production") {
+ return null;
+ }
+
+ const domain = process.env.NEXT_PUBLIC_PLAUSIBLE_DOMAIN as string;
+ if (!domain) {
+ return null;
+ }
+
+ return (
+
+ );
+}
diff --git a/src/analytics/umami-analytics.tsx b/src/analytics/umami-analytics.tsx
new file mode 100644
index 0000000..284727f
--- /dev/null
+++ b/src/analytics/umami-analytics.tsx
@@ -0,0 +1,28 @@
+"use client";
+
+import Script from "next/script";
+
+/**
+ * Umami Analytics
+ *
+ * https://umami.is
+ */
+export function UmamiAnalytics() {
+ if (process.env.NODE_ENV !== "production") {
+ return null;
+ }
+
+ const websiteId = process.env.NEXT_PUBLIC_UMAMI_WEBSITE_ID as string;
+ if (!websiteId) {
+ return null;
+ }
+
+ return (
+
+ );
+}
\ No newline at end of file
diff --git a/src/app/[locale]/layout.tsx b/src/app/[locale]/layout.tsx
index 551fc91..fbca4b1 100644
--- a/src/app/[locale]/layout.tsx
+++ b/src/app/[locale]/layout.tsx
@@ -8,6 +8,8 @@ import { Toaster } from 'sonner';
import { Providers } from './providers';
import '@/styles/globals.css';
+import { TailwindIndicator } from '@/components/layout/tailwind-indicator';
+import { Analytics } from '@/analytics/analytics';
interface LocaleLayoutProps {
children: ReactNode;
@@ -49,8 +51,8 @@ export default async function LocaleLayout({
{children}
-
- {/* */}
+
+