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
|
# Internationalization
|
||||||
NEXT_INTL_DEFAULT_LOCALE="zh"
|
NEXT_INTL_DEFAULT_LOCALE="zh"
|
||||||
NEXT_INTL_SUPPORTED_LOCALES="zh,en"
|
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": {
|
"dependencies": {
|
||||||
"@prisma/client": "^6.1.0",
|
"@prisma/client": "^6.1.0",
|
||||||
"@vercel/analytics": "^1.6.1",
|
|
||||||
"@vercel/speed-insights": "^1.3.1",
|
|
||||||
"lucide-react": "^0.468.0",
|
"lucide-react": "^0.468.0",
|
||||||
"next": "15.1.11",
|
"next": "15.1.11",
|
||||||
"next-intl": "^4.0.2",
|
"next-intl": "^4.0.2",
|
||||||
|
|||||||
Generated
-65
@@ -11,12 +11,6 @@ importers:
|
|||||||
'@prisma/client':
|
'@prisma/client':
|
||||||
specifier: ^6.1.0
|
specifier: ^6.1.0
|
||||||
version: 6.19.1(prisma@6.19.1(typescript@5.9.3))(typescript@5.9.3)
|
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:
|
lucide-react:
|
||||||
specifier: ^0.468.0
|
specifier: ^0.468.0
|
||||||
version: 0.468.0(react@19.2.3)
|
version: 0.468.0(react@19.2.3)
|
||||||
@@ -1044,55 +1038,6 @@ packages:
|
|||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [win32]
|
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':
|
'@vitest/expect@2.1.9':
|
||||||
resolution: {integrity: sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==}
|
resolution: {integrity: sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==}
|
||||||
|
|
||||||
@@ -3797,16 +3742,6 @@ snapshots:
|
|||||||
'@unrs/resolver-binding-win32-x64-msvc@1.11.1':
|
'@unrs/resolver-binding-win32-x64-msvc@1.11.1':
|
||||||
optional: true
|
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':
|
'@vitest/expect@2.1.9':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@vitest/spy': 2.1.9
|
'@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 type { Metadata } from "next"
|
||||||
import { VercelMetrics } from "./VercelMetrics"
|
import { UmamiMetrics } from "./UmamiMetrics"
|
||||||
import "./globals.css"
|
import "./globals.css"
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
@@ -7,8 +7,6 @@ export const metadata: Metadata = {
|
|||||||
description: "发现和探索全网优质 AI 项目",
|
description: "发现和探索全网优质 AI 项目",
|
||||||
}
|
}
|
||||||
|
|
||||||
const isVercelProduction = process.env.VERCEL_ENV === "production"
|
|
||||||
|
|
||||||
export default function RootLayout({
|
export default function RootLayout({
|
||||||
children,
|
children,
|
||||||
}: Readonly<{
|
}: Readonly<{
|
||||||
@@ -18,7 +16,7 @@ export default function RootLayout({
|
|||||||
<html lang="zh" suppressHydrationWarning>
|
<html lang="zh" suppressHydrationWarning>
|
||||||
<body className="font-sans antialiased">
|
<body className="font-sans antialiased">
|
||||||
{children}
|
{children}
|
||||||
{isVercelProduction ? <VercelMetrics /> : null}
|
<UmamiMetrics />
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user