fix: disable Vercel analytics outside Vercel

This commit is contained in:
kuekhaoyang
2026-07-09 15:07:34 +08:00
parent 89d1d7268b
commit 93e6699937
8 changed files with 80 additions and 25 deletions
+1 -1
View File
@@ -765,7 +765,7 @@ ALLOW_LAN_ACCESS=true LAN_ALLOWED_DEV_ORIGINS="kvideo.lan,192.168.50.10" npm run
- **ESLint 10**:代码质量检查 - **ESLint 10**:代码质量检查
- **PostCSS 8**CSS 处理器 - **PostCSS 8**CSS 处理器
- **Vercel Analytics**:性能监控和分析 - **Vercel Analytics**仅在 Vercel 部署中启用的性能监控和分析
- **Cloudflare Pages**:边缘部署支持 - **Cloudflare Pages**:边缘部署支持
### 架构特点 ### 架构特点
+11 -1
View File
@@ -4,8 +4,18 @@
"name": "KVideo", "name": "KVideo",
"branch": "main" "branch": "main"
}, },
"currentVersion": "4.9.4", "currentVersion": "4.9.5",
"releases": [ "releases": [
{
"version": "4.9.5",
"publishedAt": "2026-07-09",
"title": "修复 Docker 登录页 Analytics 404",
"notes": [
"Vercel Analytics 现在只会在真实 Vercel 部署中注入,Docker、自托管和 Cloudflare Pages 不再请求 /_vercel/insights/script.js。",
"Cloudflare 部署环境判定优先于 Vercel 风格环境变量,避免适配器环境被误判为 Vercel。",
"新增部署环境单元测试,覆盖自托管、Vercel、Cloudflare 和混合环境变量场景。"
]
},
{ {
"version": "4.9.4", "version": "4.9.4",
"publishedAt": "2026-07-08", "publishedAt": "2026-07-08",
+3 -1
View File
@@ -16,6 +16,7 @@ import { ScrollPositionManager } from "@/components/ScrollPositionManager";
import { LocaleProvider } from "@/components/LocaleProvider"; import { LocaleProvider } from "@/components/LocaleProvider";
import { RuntimeFeaturesProvider } from "@/components/RuntimeFeaturesProvider"; import { RuntimeFeaturesProvider } from "@/components/RuntimeFeaturesProvider";
import { VideoTogetherController } from '@/components/VideoTogetherController'; import { VideoTogetherController } from '@/components/VideoTogetherController';
import { shouldEnableVercelAnalytics } from '@/lib/config/deployment';
import { getRuntimeFeatures } from "@/lib/server/runtime-features"; import { getRuntimeFeatures } from "@/lib/server/runtime-features";
import { resolveSiteIconSrc } from '@/lib/server/site-icon'; import { resolveSiteIconSrc } from '@/lib/server/site-icon';
import fs from 'fs'; import fs from 'fs';
@@ -86,6 +87,7 @@ export default async function RootLayout({
process.env.VIDEOTOGETHER_SCRIPT_URL?.trim() || DEFAULT_VIDEOTOGETHER_SCRIPT_URL; process.env.VIDEOTOGETHER_SCRIPT_URL?.trim() || DEFAULT_VIDEOTOGETHER_SCRIPT_URL;
const videoTogetherSettingUrl = process.env.VIDEOTOGETHER_SETTING_URL?.trim(); const videoTogetherSettingUrl = process.env.VIDEOTOGETHER_SETTING_URL?.trim();
const videoTogetherEnvEnabled = process.env.VIDEOTOGETHER_ENABLED !== 'false'; const videoTogetherEnvEnabled = process.env.VIDEOTOGETHER_ENABLED !== 'false';
const vercelAnalyticsEnabled = shouldEnableVercelAnalytics();
return ( return (
<html lang="zh-CN" suppressHydrationWarning> <html lang="zh-CN" suppressHydrationWarning>
@@ -136,7 +138,7 @@ export default async function RootLayout({
<ScrollPositionManager /> <ScrollPositionManager />
</PasswordGate> </PasswordGate>
</TVProvider> </TVProvider>
<Analytics /> {vercelAnalyticsEnabled ? <Analytics /> : null}
<ServiceWorkerRegister /> <ServiceWorkerRegister />
</RuntimeFeaturesProvider> </RuntimeFeaturesProvider>
</ThemeProvider> </ThemeProvider>
+19
View File
@@ -0,0 +1,19 @@
type EnvLike = Record<string, string | undefined>;
export function isVercelDeployment(env: EnvLike = process.env): boolean {
return env.VERCEL === '1' || Boolean(env.VERCEL_ENV);
}
export function isCloudflareDeployment(env: EnvLike = process.env): boolean {
return (
env.CF_PAGES === '1' ||
Boolean(env.CF_PAGES_URL) ||
Boolean(env.CLOUDFLARE_ACCOUNT_ID) ||
Boolean(env.CF_ACCOUNT_ID) ||
Boolean(env.WORKERS_CI)
);
}
export function shouldEnableVercelAnalytics(env: EnvLike = process.env): boolean {
return isVercelDeployment(env) && !isCloudflareDeployment(env);
}
+5 -19
View File
@@ -1,20 +1,7 @@
import 'server-only'; import 'server-only';
import type { RuntimeFeatures } from '@/lib/config/runtime-features'; import type { RuntimeFeatures } from '@/lib/config/runtime-features';
import { isCloudflareDeployment, isVercelDeployment } from '@/lib/config/deployment';
function isVercelDeployment(): boolean {
return process.env.VERCEL === '1' || Boolean(process.env.VERCEL_ENV);
}
function isCloudflareDeployment(): boolean {
return (
process.env.CF_PAGES === '1' ||
Boolean(process.env.CF_PAGES_URL) ||
Boolean(process.env.CLOUDFLARE_ACCOUNT_ID) ||
Boolean(process.env.CF_ACCOUNT_ID) ||
Boolean(process.env.WORKERS_CI)
);
}
function getRestrictedFeatures( function getRestrictedFeatures(
deploymentProvider: RuntimeFeatures['deploymentProvider'], deploymentProvider: RuntimeFeatures['deploymentProvider'],
@@ -31,14 +18,14 @@ function getRestrictedFeatures(
} }
export function getRuntimeFeatures(): RuntimeFeatures { export function getRuntimeFeatures(): RuntimeFeatures {
if (isVercelDeployment()) {
return getRestrictedFeatures('vercel', 'Vercel');
}
if (isCloudflareDeployment()) { if (isCloudflareDeployment()) {
return getRestrictedFeatures('cloudflare', 'Cloudflare'); return getRestrictedFeatures('cloudflare', 'Cloudflare');
} }
if (isVercelDeployment()) {
return getRestrictedFeatures('vercel', 'Vercel');
}
return { return {
deploymentProvider: 'self-hosted', deploymentProvider: 'self-hosted',
deploymentProviderLabel: '自托管', deploymentProviderLabel: '自托管',
@@ -48,4 +35,3 @@ export function getRuntimeFeatures(): RuntimeFeatures {
restrictionSummary: null, restrictionSummary: null,
}; };
} }
+2 -2
View File
@@ -1,12 +1,12 @@
{ {
"name": "kvideo", "name": "kvideo",
"version": "4.9.4", "version": "4.9.5",
"lockfileVersion": 2, "lockfileVersion": 2,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "kvideo", "name": "kvideo",
"version": "4.9.4", "version": "4.9.5",
"dependencies": { "dependencies": {
"@dnd-kit/core": "^6.3.1", "@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0", "@dnd-kit/sortable": "^10.0.0",
+1 -1
View File
@@ -1,6 +1,6 @@
{ {
"name": "kvideo", "name": "kvideo",
"version": "4.9.4", "version": "4.9.5",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "node scripts/next-with-lan-access.mjs dev", "dev": "node scripts/next-with-lan-access.mjs dev",
+38
View File
@@ -0,0 +1,38 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import {
isCloudflareDeployment,
isVercelDeployment,
shouldEnableVercelAnalytics,
} from '@/lib/config/deployment';
test('self-hosted deployments do not enable Vercel Analytics', () => {
const env = {};
assert.equal(isVercelDeployment(env), false);
assert.equal(isCloudflareDeployment(env), false);
assert.equal(shouldEnableVercelAnalytics(env), false);
});
test('Vercel deployments enable Vercel Analytics', () => {
assert.equal(shouldEnableVercelAnalytics({ VERCEL: '1' }), true);
assert.equal(shouldEnableVercelAnalytics({ VERCEL_ENV: 'production' }), true);
});
test('Cloudflare deployments do not enable Vercel Analytics', () => {
assert.equal(shouldEnableVercelAnalytics({ CF_PAGES: '1' }), false);
assert.equal(shouldEnableVercelAnalytics({ CF_PAGES_URL: 'https://kvideo.pages.dev' }), false);
});
test('Cloudflare wins when adapter environments expose Vercel-like variables', () => {
const env = {
VERCEL: '1',
VERCEL_ENV: 'production',
CF_PAGES: '1',
};
assert.equal(isVercelDeployment(env), true);
assert.equal(isCloudflareDeployment(env), true);
assert.equal(shouldEnableVercelAnalytics(env), false);
});