feat: support analytics (google/umami/plausible/datafast/openpanel)

This commit is contained in:
javayhu 2025-04-11 23:54:05 +08:00
parent 257feba5bd
commit c04499a353
10 changed files with 254 additions and 2 deletions

View File

@ -67,3 +67,27 @@ NEXT_PUBLIC_STRIPE_PRICE_PRO_MONTHLY=""
NEXT_PUBLIC_STRIPE_PRICE_PRO_YEARLY="" NEXT_PUBLIC_STRIPE_PRICE_PRO_YEARLY=""
# Lifetime plan - one-time payment # Lifetime plan - one-time payment
NEXT_PUBLIC_STRIPE_PRICE_LIFETIME="" 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=""

View File

@ -28,6 +28,8 @@
"@fumadocs/content-collections": "^1.1.8", "@fumadocs/content-collections": "^1.1.8",
"@hookform/resolvers": "^4.1.0", "@hookform/resolvers": "^4.1.0",
"@neondatabase/serverless": "^0.10.4", "@neondatabase/serverless": "^0.10.4",
"@next/third-parties": "^15.3.0",
"@openpanel/nextjs": "^1.0.7",
"@orama/orama": "^3.1.4", "@orama/orama": "^3.1.4",
"@orama/tokenizers": "^3.1.4", "@orama/tokenizers": "^3.1.4",
"@radix-ui/react-accordion": "^1.2.3", "@radix-ui/react-accordion": "^1.2.3",

49
pnpm-lock.yaml generated
View File

@ -47,6 +47,12 @@ importers:
'@neondatabase/serverless': '@neondatabase/serverless':
specifier: ^0.10.4 specifier: ^0.10.4
version: 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': '@orama/orama':
specifier: ^3.1.4 specifier: ^3.1.4
version: 3.1.4 version: 3.1.4
@ -1809,6 +1815,12 @@ packages:
cpu: [x64] cpu: [x64]
os: [win32] 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': '@noble/ciphers@0.6.0':
resolution: {integrity: sha512-mIbq/R9QXk5/cTfESb1OKtyFnk7oc1Om/8onA1158K9/OZUQFDEVy55jVTato+xmp3XX6F6Qh0zz0Nc1AxAlRQ==} resolution: {integrity: sha512-mIbq/R9QXk5/cTfESb1OKtyFnk7oc1Om/8onA1158K9/OZUQFDEVy55jVTato+xmp3XX6F6Qh0zz0Nc1AxAlRQ==}
@ -1819,6 +1831,19 @@ packages:
'@one-ini/wasm@0.1.1': '@one-ini/wasm@0.1.1':
resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==} 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': '@opentelemetry/api@1.9.0':
resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==}
engines: {node: '>=8.0.0'} engines: {node: '>=8.0.0'}
@ -5265,6 +5290,9 @@ packages:
text-table@0.2.0: text-table@0.2.0:
resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==}
third-party-capital@1.0.20:
resolution: {integrity: sha512-oB7yIimd8SuGptespDAZnNkzIz+NWaJCu2RMsbs4Wmp9zSDUM8Nhi3s2OOcqYuv3mN4hitXc8DVx+LyUmbUDiA==}
throttleit@2.1.0: throttleit@2.1.0:
resolution: {integrity: sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==} resolution: {integrity: sha512-nt6AMGKW1p/70DF/hGBdJB57B8Tspmbp5gfJ8ilhLnt7kkr2ye7hzD6NVG8GGErk2HWF34igrL2CXmNIkzKqKw==}
engines: {node: '>=18'} engines: {node: '>=18'}
@ -6963,12 +6991,31 @@ snapshots:
'@next/swc-win32-x64-msvc@15.2.1': '@next/swc-win32-x64-msvc@15.2.1':
optional: true 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/ciphers@0.6.0': {}
'@noble/hashes@1.7.1': {} '@noble/hashes@1.7.1': {}
'@one-ini/wasm@0.1.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': {} '@opentelemetry/api@1.9.0': {}
'@orama/orama@3.1.4': {} '@orama/orama@3.1.4': {}
@ -11112,6 +11159,8 @@ snapshots:
text-table@0.2.0: {} text-table@0.2.0: {}
third-party-capital@1.0.20: {}
throttleit@2.1.0: {} throttleit@2.1.0: {}
tiny-invariant@1.3.3: {} tiny-invariant@1.3.3: {}

View File

@ -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 */}
<GoogleAnalytics />
{/* umami analytics */}
<UmamiAnalytics />
{/* plausible analytics */}
<PlausibleAnalytics />
{/* datafast analytics */}
<DataFastAnalytics />
{/* openpanel analytics */}
<OpenPanelAnalytics />
</>
);
}

View File

@ -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 (
<>
<Script
defer
data-website-id={websiteId}
data-domain={domain}
src="https://datafa.st/js/script.js"
/>
</>
);
}

View File

@ -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 <NextGoogleAnalytics gaId={analyticsId} />;
}

View File

@ -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 (
<OpenPanelComponent
clientId={clientId}
trackScreenViews={true}
trackAttributes={true}
trackOutgoingLinks={true}
/>
);
}

View File

@ -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 (
<Script
defer
type="text/javascript"
data-domain={domain}
src="https://plausible.io/js/script.js"
/>
);
}

View File

@ -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 (
<Script
async
type="text/javascript"
data-website-id={websiteId}
src="https://cloud.umami.is/script.js"
/>
);
}

View File

@ -8,6 +8,8 @@ import { Toaster } from 'sonner';
import { Providers } from './providers'; import { Providers } from './providers';
import '@/styles/globals.css'; import '@/styles/globals.css';
import { TailwindIndicator } from '@/components/layout/tailwind-indicator';
import { Analytics } from '@/analytics/analytics';
interface LocaleLayoutProps { interface LocaleLayoutProps {
children: ReactNode; children: ReactNode;
@ -49,8 +51,8 @@ export default async function LocaleLayout({
{children} {children}
<Toaster richColors position="top-right" offset={64} /> <Toaster richColors position="top-right" offset={64} />
<TailwindIndicator />
{/* <TailwindIndicator /> */} <Analytics />
</Providers> </Providers>
</NextIntlClientProvider> </NextIntlClientProvider>
</body> </body>