feat: switch site analytics to umami
This commit is contained in:
@@ -10,3 +10,8 @@ N8N_AI_SEARCH_WEBHOOK="https://n8n.mzaxd.fun/webhook/ai-search"
|
||||
# Internationalization
|
||||
NEXT_INTL_DEFAULT_LOCALE="zh"
|
||||
NEXT_INTL_SUPPORTED_LOCALES="zh,en"
|
||||
|
||||
# Optional self-hosted analytics
|
||||
NEXT_PUBLIC_UMAMI_HOST_URL="https://analytics.example.com"
|
||||
NEXT_PUBLIC_UMAMI_WEBSITE_ID="your-umami-website-id"
|
||||
NEXT_PUBLIC_UMAMI_DOMAINS="example.com,www.example.com"
|
||||
|
||||
@@ -14,8 +14,6 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@prisma/client": "^6.1.0",
|
||||
"@vercel/analytics": "^1.6.1",
|
||||
"@vercel/speed-insights": "^1.3.1",
|
||||
"lucide-react": "^0.468.0",
|
||||
"next": "15.1.11",
|
||||
"next-intl": "^4.0.2",
|
||||
|
||||
Generated
-65
@@ -11,12 +11,6 @@ importers:
|
||||
'@prisma/client':
|
||||
specifier: ^6.1.0
|
||||
version: 6.19.1(prisma@6.19.1(typescript@5.9.3))(typescript@5.9.3)
|
||||
'@vercel/analytics':
|
||||
specifier: ^1.6.1
|
||||
version: 1.6.1(next@15.1.11(@playwright/test@1.57.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)
|
||||
'@vercel/speed-insights':
|
||||
specifier: ^1.3.1
|
||||
version: 1.3.1(next@15.1.11(@playwright/test@1.57.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)
|
||||
lucide-react:
|
||||
specifier: ^0.468.0
|
||||
version: 0.468.0(react@19.2.3)
|
||||
@@ -1044,55 +1038,6 @@ packages:
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@vercel/analytics@1.6.1':
|
||||
resolution: {integrity: sha512-oH9He/bEM+6oKlv3chWuOOcp8Y6fo6/PSro8hEkgCW3pu9/OiCXiUpRUogDh3Fs3LH2sosDrx8CxeOLBEE+afg==}
|
||||
peerDependencies:
|
||||
'@remix-run/react': ^2
|
||||
'@sveltejs/kit': ^1 || ^2
|
||||
next: '>= 13'
|
||||
react: ^18 || ^19 || ^19.0.0-rc
|
||||
svelte: '>= 4'
|
||||
vue: ^3
|
||||
vue-router: ^4
|
||||
peerDependenciesMeta:
|
||||
'@remix-run/react':
|
||||
optional: true
|
||||
'@sveltejs/kit':
|
||||
optional: true
|
||||
next:
|
||||
optional: true
|
||||
react:
|
||||
optional: true
|
||||
svelte:
|
||||
optional: true
|
||||
vue:
|
||||
optional: true
|
||||
vue-router:
|
||||
optional: true
|
||||
|
||||
'@vercel/speed-insights@1.3.1':
|
||||
resolution: {integrity: sha512-PbEr7FrMkUrGYvlcLHGkXdCkxnylCWePx7lPxxq36DNdfo9mcUjLOmqOyPDHAOgnfqgGGdmE3XI9L/4+5fr+vQ==}
|
||||
peerDependencies:
|
||||
'@sveltejs/kit': ^1 || ^2
|
||||
next: '>= 13'
|
||||
react: ^18 || ^19 || ^19.0.0-rc
|
||||
svelte: '>= 4'
|
||||
vue: ^3
|
||||
vue-router: ^4
|
||||
peerDependenciesMeta:
|
||||
'@sveltejs/kit':
|
||||
optional: true
|
||||
next:
|
||||
optional: true
|
||||
react:
|
||||
optional: true
|
||||
svelte:
|
||||
optional: true
|
||||
vue:
|
||||
optional: true
|
||||
vue-router:
|
||||
optional: true
|
||||
|
||||
'@vitest/expect@2.1.9':
|
||||
resolution: {integrity: sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==}
|
||||
|
||||
@@ -3797,16 +3742,6 @@ snapshots:
|
||||
'@unrs/resolver-binding-win32-x64-msvc@1.11.1':
|
||||
optional: true
|
||||
|
||||
'@vercel/analytics@1.6.1(next@15.1.11(@playwright/test@1.57.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)':
|
||||
optionalDependencies:
|
||||
next: 15.1.11(@playwright/test@1.57.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
react: 19.2.3
|
||||
|
||||
'@vercel/speed-insights@1.3.1(next@15.1.11(@playwright/test@1.57.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3))(react@19.2.3)':
|
||||
optionalDependencies:
|
||||
next: 15.1.11(@playwright/test@1.57.0)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)
|
||||
react: 19.2.3
|
||||
|
||||
'@vitest/expect@2.1.9':
|
||||
dependencies:
|
||||
'@vitest/spy': 2.1.9
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import Script from "next/script";
|
||||
|
||||
const beforeSendScript = `
|
||||
window.umamiBeforeSend = function (_type, payload) {
|
||||
try {
|
||||
var rawUrl = payload && typeof payload.url === "string" ? payload.url : window.location.href;
|
||||
var pathname = new URL(rawUrl, window.location.origin).pathname;
|
||||
var isTrackable =
|
||||
pathname === "/zh" ||
|
||||
pathname === "/en" ||
|
||||
pathname.startsWith("/zh/") ||
|
||||
pathname.startsWith("/en/");
|
||||
|
||||
return isTrackable ? payload : false;
|
||||
} catch (_error) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
`;
|
||||
|
||||
export function UmamiMetrics() {
|
||||
const hostUrl = process.env.NEXT_PUBLIC_UMAMI_HOST_URL?.replace(/\/+$/, "");
|
||||
const websiteId = process.env.NEXT_PUBLIC_UMAMI_WEBSITE_ID;
|
||||
const domains = process.env.NEXT_PUBLIC_UMAMI_DOMAINS;
|
||||
|
||||
if (!hostUrl || !websiteId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Script id="umami-before-send" strategy="beforeInteractive">
|
||||
{beforeSendScript}
|
||||
</Script>
|
||||
<Script
|
||||
id="umami-script"
|
||||
src={`${hostUrl}/script.js`}
|
||||
strategy="afterInteractive"
|
||||
data-website-id={websiteId}
|
||||
data-host-url={hostUrl}
|
||||
data-domains={domains || undefined}
|
||||
data-before-send="umamiBeforeSend"
|
||||
data-exclude-search="true"
|
||||
data-exclude-hash="true"
|
||||
data-do-not-track="true"
|
||||
data-performance="true"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
"use client"
|
||||
|
||||
import { Analytics } from "@vercel/analytics/next"
|
||||
import { SpeedInsights } from "@vercel/speed-insights/next"
|
||||
|
||||
const isTrackablePath = (url: string): boolean => {
|
||||
try {
|
||||
const pathname = new URL(url).pathname
|
||||
|
||||
// Only keep user-facing locale pages to control usage on the Hobby plan.
|
||||
return pathname === "/zh" || pathname === "/en" || pathname.startsWith("/zh/") || pathname.startsWith("/en/")
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
export function VercelMetrics() {
|
||||
return (
|
||||
<>
|
||||
<Analytics mode="production" beforeSend={(event) => (isTrackablePath(event.url) ? event : null)} />
|
||||
<SpeedInsights sampleRate={0.2} beforeSend={(data) => (isTrackablePath(data.url) ? data : null)} />
|
||||
</>
|
||||
)
|
||||
}
|
||||
+2
-4
@@ -1,5 +1,5 @@
|
||||
import type { Metadata } from "next"
|
||||
import { VercelMetrics } from "./VercelMetrics"
|
||||
import { UmamiMetrics } from "./UmamiMetrics"
|
||||
import "./globals.css"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
@@ -7,8 +7,6 @@ export const metadata: Metadata = {
|
||||
description: "发现和探索全网优质 AI 项目",
|
||||
}
|
||||
|
||||
const isVercelProduction = process.env.VERCEL_ENV === "production"
|
||||
|
||||
export default function RootLayout({
|
||||
children,
|
||||
}: Readonly<{
|
||||
@@ -18,7 +16,7 @@ export default function RootLayout({
|
||||
<html lang="zh" suppressHydrationWarning>
|
||||
<body className="font-sans antialiased">
|
||||
{children}
|
||||
{isVercelProduction ? <VercelMetrics /> : null}
|
||||
<UmamiMetrics />
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user