diff --git a/.env.example b/.env.example index 490b0f0..f973d3a 100644 --- a/.env.example +++ b/.env.example @@ -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" diff --git a/package.json b/package.json index f832e9b..d9347ba 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 88e67e4..5647966 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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 diff --git a/src/app/UmamiMetrics.tsx b/src/app/UmamiMetrics.tsx new file mode 100644 index 0000000..d789346 --- /dev/null +++ b/src/app/UmamiMetrics.tsx @@ -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 ( + <> + +