diff --git a/README.md b/README.md index 8c20147..a11eaa4 100644 --- a/README.md +++ b/README.md @@ -250,7 +250,11 @@ SMOKE_PUBLIC_ONLY=1 PREVIEW_URL=http://127.0.0.1:21021 npm run smoke:api - `tests/routing.test.mjs`:路径解析、搜索关键词编码、hash 地址不转换与原生页面导航测试。 - `src/lib/client/startup.ts` 与 `tests/startup.test.mjs`:年龄确认、广告队列和会话进度,以及控制器与浏览器事件适配测试。 +## 访问统计 + +生产环境(`APP_ENV=production`)在全部页面加载 Google Analytics 4(`G-7ZYCTGDJ4F`)和 Yandex.Metrika(`113827715`,含 webvisor、clickmap 及 noscript 像素),代码在 `src/components/site/SiteAnalytics.tsx`,由根布局引入;开发、测试环境不加载,避免污染统计数据。页面均为原生整页跳转,每次加载各自初始化,无需额外发送路由 hit。 + ## 已删除的功能 -已移除 Web SDK 埋点(含 `X-SDK-Common`、`X-Tracker-*` 请求头透传)、意见反馈/在线客服、充值与 VIP、个人中心(收藏/历史/点赞/已购/设置)、登录注册以及付费章节解锁。对应页面返回 404,代理白名单不再放行 `auth/login`、`auth/register`、`auth/resetPassword`、`auth/profile`、`coin/*`、`vip/*`、`recharge/*`、`playlet/chapter/unlock`、个人列表和 `upload/image`。 +已移除原有的 Web SDK 埋点(含 `X-SDK-Common`、`X-Tracker-*` 请求头透传)、意见反馈/在线客服、充值与 VIP、个人中心(收藏/历史/点赞/已购/设置)、登录注册以及付费章节解锁。对应页面返回 404,代理白名单不再放行 `auth/login`、`auth/register`、`auth/resetPassword`、`auth/profile`、`coin/*`、`vip/*`、`recharge/*`、`playlet/chapter/unlock`、个人列表和 `upload/image`。 diff --git a/next-env.d.ts b/next-env.d.ts index ce4e94a..6936789 100644 --- a/next-env.d.ts +++ b/next-env.d.ts @@ -1,7 +1,7 @@ /// /// -import "./.next/types/routes.d.ts"; -import "./.next/types/root-params.d.ts"; +import "./.next-dev/dev/types/routes.d.ts"; +import "./.next-dev/dev/types/root-params.d.ts"; // NOTE: This file should not be edited // see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 1138169..ffc03dc 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,5 +1,6 @@ import type { Metadata, Viewport } from "next"; import "./globals.css"; +import { SiteAnalytics } from "@/components/site/SiteAnalytics"; export const metadata: Metadata = { title: "黄果吃瓜 · 百万大神原创短剧", @@ -17,7 +18,7 @@ export const viewport: Viewport = { export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) { return ( - {children} + {children} ); } diff --git a/src/components/site/SiteAnalytics.tsx b/src/components/site/SiteAnalytics.tsx new file mode 100644 index 0000000..a8d68cd --- /dev/null +++ b/src/components/site/SiteAnalytics.tsx @@ -0,0 +1,40 @@ +import Script from "next/script"; + +/** Google Analytics 4 */ +const GA_ID = "G-7ZYCTGDJ4F"; +/** Yandex.Metrika counter */ +const YM_ID = 113827715; + +/** + * Site statistics: GA4 + Yandex.Metrika, production only so development and test traffic is not counted. + * Every page is a native document request, so each load runs init once; no client route hits are needed. + */ +export function SiteAnalytics() { + if (process.env.APP_ENV !== "production") return null; + return ( + <> + + + + + ); +} diff --git a/tests/api-integration.test.mjs b/tests/api-integration.test.mjs index 3208bae..cb023a2 100644 --- a/tests/api-integration.test.mjs +++ b/tests/api-integration.test.mjs @@ -438,7 +438,7 @@ test('SSR emits configured TDK, site_name, canonical and bounded pagination link assert.match(head,/call.path==='/api/app/playlet/home').length,1,'SSR and metadata share one homepage request'); assert.equal(calls.slice(start).filter(call=>call.path==='/api/app/config/info').length,1,'SSR and metadata share one configuration request'); - assert.ok(!/googletagmanager|google-analytics|mc\.yandex/.test(head)); + if(testEnvironment==='production'){const html=home;assert.ok(html.includes('googletagmanager.com/gtag/js?id=G-7ZYCTGDJ4F'));assert.ok(html.includes('metrika/tag.js?id=113827715'));assert.ok(html.includes('mc.yandex.ru/watch/113827715'));} for(const agent of ['Mozilla/5.0 Chrome/130','Googlebot','facebookexternalhit']){ for(const [path,page] of [['/shorts/',1],['/shorts/page/2/',2],['/shorts/page/3/',3]]){ const response=await fetch(base+path+'?channel=campaign',{redirect:'manual',headers:{'User-Agent':agent}});