ad-tracking

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Advertising Analytics & Conversion Tracking Integration

广告分析与转化跟踪集成

Complete reference for integrating Google Analytics 4, Google Ads, Meta (Facebook) Pixel, and LinkedIn Insight Tag into web applications with proper consent management, standard event mapping, user identification, and GDPR/DMA compliance.

将 Google Analytics 4、Google Ads、Meta (Facebook) Pixel 和 LinkedIn Insight Tag 集成到 Web 应用的完整参考,涵盖正确的同意管理、标准事件映射、用户识别以及 GDPR/DMA 合规。

What this skill covers

本 skill 涵盖的内容

GA4, Google Ads, Meta Pixel and LinkedIn Insight Tag, all behind Consent Mode v2. The body carries the minimal setup and the traps; the tables, schemas and per-framework wiring live in
references/
, listed at the bottom.
GA4、Google Ads、Meta Pixel 和 LinkedIn Insight Tag,全部置于 Consent Mode v2 之下。正文提供最小设置和常见陷阱;表格、schema 以及各框架的具体接入方式位于
references/
中,文件列表见底部。

Architecture Overview

架构概览

A modern advertising analytics stack consists of multiple pixels/tags that must all respect the same consent state. The architecture follows this pattern:
┌──────────────────────────────────────────────────────────┐
│                    CONSENT LAYER                          │
│  Cookie Consent Banner → localStorage → consent state     │
│  All pixels gated by the same consent storage key         │
└────────────────────┬─────────────────────────────────────┘
                     │ granted / denied
     ┌───────────────┼──────────────────┬──────────────────┐
     ▼               ▼                  ▼                  ▼
┌─────────┐   ┌────────────┐   ┌──────────────┐   ┌────────────┐
│  GA4 +   │   │  Meta      │   │  LinkedIn    │   │  Mixpanel  │
│  Google  │   │  Pixel     │   │  Insight     │   │  (product  │
│  Ads     │   │  (fbq)     │   │  Tag         │   │  analytics)│
└─────────┘   └────────────┘   └──────────────┘   └────────────┘
     │               │                  │                  │
     ▼               ▼                  ▼                  ▼
  Consent         Consent-gated     Consent-gated      Consent-gated
  Mode v2         (render only      (render only        (opt_in /
  (built-in)      when granted)     when granted)       opt_out)
Key principle: GA4 uses its own built-in Consent Mode v2 (defaults denied, updates on consent). All other pixels (Meta, LinkedIn, Mixpanel) use a consent-gated pattern — they are not rendered at all until the user grants consent. A shared
CustomEvent
allows dynamic consent changes without page reload.

现代广告分析栈由多个像素/标签构成,且它们必须遵循同一个同意状态。架构采用以下模式:
┌──────────────────────────────────────────────────────────┐
│                    CONSENT LAYER                          │
│  Cookie Consent Banner → localStorage → consent state     │
│  All pixels gated by the same consent storage key         │
└────────────────────┬─────────────────────────────────────┘
                     │ granted / denied
     ┌───────────────┼──────────────────┬──────────────────┐
     ▼               ▼                  ▼                  ▼
┌─────────┐   ┌────────────┐   ┌──────────────┐   ┌────────────┐
│  GA4 +   │   │  Meta      │   │  LinkedIn    │   │  Mixpanel  │
│  Google  │   │  Pixel     │   │  Insight     │   │  (product  │
│  Ads     │   │  (fbq)     │   │  Tag         │   │  analytics)│
└─────────┘   └────────────┘   └──────────────┘   └────────────┘
     │               │                  │                  │
     ▼               ▼                  ▼                  ▼
  Consent         Consent-gated     Consent-gated      Consent-gated
  Mode v2         (render only      (render only        (opt_in /
  (built-in)      when granted)     when granted)       opt_out)
核心原则: GA4 使用其内置的 Consent Mode v2(默认拒绝,并在同意变化时更新)。其他所有像素(Meta、LinkedIn、Mixpanel)均采用受同意控制的模式——在用户授予同意前,完全不会渲染。通过共享的
CustomEvent
,可在无需重新加载页面的情况下动态处理同意变化。

Consent

同意

Read
references/consent-mode.md
for all seven consent signals, Advanced vs Basic mode, region-specific defaults, granular consent and GTM wiring. Consent Mode v2 is mandatory in the EU/EEA since March 2024.
The order is the whole thing, and getting it wrong is invisible:
1. dataLayer init + gtag() stub
2. gtag('consent', 'default', { …'denied', wait_for_update: 500 })
3. restore a stored decision → gtag('consent', 'update', …)
4. load gtag.js (async)
5. gtag('js', new Date()) + gtag('config', TAG_ID)
Steps 1–3 must run synchronously before gtag.js loads. A page that sets defaults after the script has loaded looks correct in every test that begins by accepting consent, and loses the denied population entirely.
Denied is not off. With Advanced mode Google still receives cookieless pings and models conversions from them (Google's own dated case studies cite ~65–70%; yours is unknown until measured). Whether pre-consent pings are lawful in your jurisdiction is counsel's call — Basic is the conservative mode, and it still gets Google's general modeling.
有关全部七项同意信号、Advanced 与 Basic 模式、地区特定默认值、细粒度同意以及 GTM 接入,请阅读
references/consent-mode.md
。自 2024 年 3 月起,Consent Mode v2 在 EU/EEA 已是强制要求。
执行顺序决定一切,而且顺序错误往往不易察觉:
1. dataLayer init + gtag() stub
2. gtag('consent', 'default', { …'denied', wait_for_update: 500 })
3. restore a stored decision → gtag('consent', 'update', …)
4. load gtag.js (async)
5. gtag('js', new Date()) + gtag('config', TAG_ID)
步骤 1–3 必须同步执行并在 gtag.js 加载之前完成。如果在脚本加载后才设置默认值,那么在每个以接受同意为起点的测试中,页面看起来都正常,却会完全丢失被拒绝的那部分用户。
拒绝并不等于关闭。 在 Advanced 模式下,Google 仍会接收无 Cookie 的 ping,并据此对转化进行建模(Google 自己带日期的案例研究给出的比例约为 65–70%;你的实际比例在测量前未知)。这些同意前 ping 在你所在司法辖区是否合法,需要由法律顾问判断——Basic 是更保守的模式,而且仍会使用 Google 的通用建模。

Google Analytics 4

Google Analytics 4

Setup

设置

Env var:
NEXT_PUBLIC_GA_MEASUREMENT_ID
(format:
G-XXXXXXXXXX
)
Script loading:
html
<!-- 1. Consent defaults (MUST be first) -->
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('consent', 'default', {
    ad_storage: 'denied', ad_user_data: 'denied',
    ad_personalization: 'denied', analytics_storage: 'denied',
    wait_for_update: 500
  });
  // Restore prior consent
  try {
    if (localStorage.getItem('consent_key') === 'granted') {
      gtag('consent', 'update', {
        ad_storage: 'granted', ad_user_data: 'granted',
        ad_personalization: 'granted', analytics_storage: 'granted'
      });
    }
  } catch(e) {}
</script>

<!-- 2. Load gtag.js -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>

<!-- 3. Configure -->
<script>
  gtag('js', new Date());
  // GA4 config carries NO allow_enhanced_conversions — that flag belongs on the
  // Google Ads `AW-` tag only (see the Enhanced-conversions section); on a `G-`
  // tag it enables nothing and contradicts this skill's own rule.
  gtag('config', 'G-XXXXXXXXXX');
</script>
环境变量:
NEXT_PUBLIC_GA_MEASUREMENT_ID
(格式:
G-XXXXXXXXXX
)
脚本加载:
html
<!-- 1. Consent defaults (MUST be first) -->
<script>
  window.dataLayer = window.dataLayer || [];
  function gtag(){dataLayer.push(arguments);}
  gtag('consent', 'default', {
    ad_storage: 'denied', ad_user_data: 'denied',
    ad_personalization: 'denied', analytics_storage: 'denied',
    wait_for_update: 500
  });
  // Restore prior consent
  try {
    if (localStorage.getItem('consent_key') === 'granted') {
      gtag('consent', 'update', {
        ad_storage: 'granted', ad_user_data: 'granted',
        ad_personalization: 'granted', analytics_storage: 'granted'
      });
    }
  } catch(e) {}
</script>

<!-- 2. Load gtag.js -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>

<!-- 3. Configure -->
<script>
  gtag('js', new Date());
  // GA4 config carries NO allow_enhanced_conversions — that flag belongs on the
  // Google Ads `AW-` tag only (see the Enhanced-conversions section); on a `G-`
  // tag it enables nothing and contradicts this skill's own rule.
  gtag('config', 'G-XXXXXXXXXX');
</script>

Commands and events

命令与事件

Read
references/gtag-api.md
→ Commands for
config
/
event
/
set
/
get
/
consent
with parameter scope and precedence, and
references/event-tracking.md
→ Recommended Events for the GA4 names Google Ads can import (
sign_up
,
login
,
begin_checkout
,
purchase
,
generate_lead
,
view_item
) with their required parameters.
SPA page views are not automatic when you send them yourself. Enhanced measurement fires
page_view
on History API changes; adding a manual one on route change double-counts every navigation. Send manual page views only for hash routing.
有关
config
/
event
/
set
/
get
/
consent
的命令、参数作用域和优先级,请阅读
references/gtag-api.md
→ 命令;有关 GA4 可供 Google Ads 导入的事件名称(
sign_up
、
login
、
begin_checkout
、
purchase
、
generate_lead
、
view_item
)及其必需参数,请阅读
references/event-tracking.md
→ 推荐事件。
当你自己发送页面浏览事件时,SPA 页面浏览不会自动发生。 Enhanced measurement 会在 History API 发生变化时触发
page_view
;在路由变化时再手动添加一次,会让每次导航都被重复计算。只有使用 hash 路由时才应手动发送页面浏览事件。

SPA Page Tracking

SPA 页面跟踪

GA4 with enhanced measurement automatically tracks
page_view
via the History API for Single Page Applications. Manual
page_view
tracking is only needed for hash routing (
#/path
). Consent state persists across SPA route changes.
Docs:

对于单页应用,启用 enhanced measurement 的 GA4 会通过 History API 自动跟踪
page_view
。只有在 hash 路由(
#/path
)下才需要手动
page_view
跟踪。同意状态会在 SPA 路由变化期间保持不变。
文档:

Google Ads Conversion Tracking

Google Ads 转化跟踪

Architecture

架构

GA4 and Google Ads share the same gtag.js snippet. When
NEXT_PUBLIC_GOOGLE_ADS_ID
(format:
AW-XXXXXXXXX
) is set, add a second
config
call:
javascript
gtag('config', 'G-XXXXXXXXXX');  // GA4
gtag('config', 'AW-XXXXXXXXX');  // Google Ads
Both share consent state, first-party cookies, and the dataLayer pipeline.
GA4 和 Google Ads 共用同一个 gtag.js 代码片段。当
NEXT_PUBLIC_GOOGLE_ADS_ID
(格式:
AW-XXXXXXXXX
)已设置时,添加第二次
config
调用:
javascript
gtag('config', 'G-XXXXXXXXXX');  // GA4
gtag('config', 'AW-XXXXXXXXX');  // Google Ads
二者共享同一同意状态、第一方 Cookie 和 dataLayer 管道。

Enhanced Conversions

增强型转化

Enhanced Conversions improve attribution by matching hashed user data (email, phone) with Google accounts. Setup:
  1. Where the flag appears at all, it belongs on the Google Ads config — the
    AW-
    tag, never the GA4
    G-
    tag
    (an earlier revision of this skill put it on the
    G-
    config, where it enables nothing for Ads):
javascript
gtag('config', 'AW-XXXXXXXXX', { allow_enhanced_conversions: true });
Checked 2026-08-30: Google's current setup docs (
support.google.com/google-ads/answer/9888145
,
…/13258081
) no longer document the flag at all — the live mechanism is the account-level toggle (step 3) plus
user_data
(step 2). Keep the flag if your tag already carries it; do not copy it onto the
G-
config.
  1. Set user data before conversion events fire:
javascript
gtag('set', 'user_data', {
  email: 'user@example.com',        // Google hashes automatically
  phone_number: '+1234567890',       // optional
});
  1. Enable in the Google Ads console: Goals → Conversions → Settings → Enhanced conversions → "Turn on enhanced conversions" (checked 2026-08-31 against
    support.google.com/google-ads/answer/13258081
    ; the old Tools → Conversions path is gone from the redesigned UI)
增强型转化通过将经过哈希处理的用户数据(电子邮件、电话)与 Google 账户匹配来改善归因。设置步骤:
  1. 只要此标志在任何地方出现,就应放在 Google Ads config——即
    AW-
    标签上,绝不能放在 GA4 的
    G-
    标签上
    (本 skill 的早期版本将其放在
    G-
    config 上,但在 Ads 中不起任何作用):
javascript
gtag('config', 'AW-XXXXXXXXX', { allow_enhanced_conversions: true });
核查于 2026-08-30:Google 当前的设置文档(
support.google.com/google-ads/answer/9888145
、
…/13258081
)已完全不再记录该标志——实际生效的机制是账户级开关(步骤 3)加
user_data
(步骤 2)。如果你的标签已经包含该标志,可以保留;不要将其复制到
G-
config 上。
  1. 在转化事件触发前设置用户数据:
javascript
gtag('set', 'user_data', {
  email: 'user@example.com',        // Google hashes automatically
  phone_number: '+1234567890',       // optional
});
  1. 在 Google Ads 控制台中启用:目标(Goals) → 转化(Conversions) → 设置(Settings) → 增强型转化(Enhanced conversions) → “打开增强型转化(Turn on enhanced conversions)”(依据
    support.google.com/google-ads/answer/13258081
    核查于 2026-08-31;重新设计后的 UI 已删除旧的工具(Tools) → 转化(Conversions)路径)。

user_id (Cross-Device Tracking)

user_id(跨设备跟踪)

Set GA4
user_id
after authentication to enable cross-device tracking and audience building:
javascript
gtag('config', 'G-XXXXXXXXXX', {
  user_id: 'USER_123',
  send_page_view: false    // prevent duplicate page_view
});
身份验证后设置 GA4
user_id
,以启用跨设备跟踪和受众群体构建:
javascript
gtag('config', 'G-XXXXXXXXXX', {
  user_id: 'USER_123',
  send_page_view: false    // prevent duplicate page_view
});

gclid Preservation Through External Redirects

通过外部重定向保留 gclid

The
gclid
from Google Ads auto-tagging is stored in the
_gcl_aw
first-party cookie (~90-day default life). It survives external redirects (Stripe Checkout → return URL) because only expiry ends a cookie on your own domain — no extra code. Checked 2026-08-31; the caveat is Safari, where ITP caps JS-set cookies at 7 days — 24 hours after a link-decorated navigation, which a gclid landing is.
Google Ads 自动标记产生的
gclid
会存储在
_gcl_aw
第一方 Cookie 中(默认有效期约 90 天)。它能在外部重定向(Stripe Checkout → 返回 URL)后保留,因为只有过期才会使你自己域名上的 Cookie 失效——无需额外代码。
核查于 2026-08-31;需要注意 Safari:ITP 会将由 JS 设置的 Cookie 限制为 7 天——即在带链接参数的导航后 24 小时,而 gclid 落地页正属于这种情况。

Google Ads console setup

Google Ads 控制台设置

Read
references/gtag-api.md
→ Multi-Product Configuration for wiring one gtag loader to both GA4 and Ads. In the Ads console the conversion action must be Primary and its attribution window set before the first click lands, or early conversions are attributed under the old settings and cannot be recomputed.
有关将一个 gtag loader 同时接入 GA4 和 Ads,请阅读
references/gtag-api.md
→ 多产品配置(Multi-Product Configuration)。在 Ads 控制台中,转化操作必须设为 Primary,且其归因窗口必须在首次点击产生前设置,否则早期转化会按旧设置归因,且无法重新计算。

Meta (Facebook) Pixel

Meta (Facebook) Pixel

Setup

设置

Env var:
NEXT_PUBLIC_META_PIXEL_ID
(numeric string)
Base code (consent-gated):
javascript
!function(f,b,e,v,n,t,s){if(f.fbq)return;n=f.fbq=function(){n.callMethod?
n.callMethod.apply(n,arguments):n.queue.push(arguments)};if(!f._fbq)f._fbq=n;
n.push=n;n.loaded=!0;n.version='2.0';n.queue=[];t=b.createElement(e);t.async=!0;
t.src=v;s=b.getElementsByTagName(e)[0];s.parentNode.insertBefore(t,s)}
(window,document,'script','https://connect.facebook.net/en_US/fbevents.js');
fbq('init', 'PIXEL_ID');
fbq('track', 'PageView');
noscript fallback — consent-gated, or omitted. A bare
<noscript>
Meta pixel FIRES on every JS-disabled page view — no banner, no
fbq
, no consent — contradicting this stack's rule that Meta is not rendered until consent. So emit it from the SERVER, only when a server-verifiable consent (a signed HttpOnly cookie the server reads before writing the HTML) already exists. Cannot verify consent server-side? Omit it — Meta's verification does not need it, and a request that fired cannot be taken back:
html
<!-- server-side: render ONLY when consent is proven -->
{% if consent_cookie_verified and consent.ad_storage == "granted" %}
<noscript><img height="1" width="1" style="display:none"
  src="https://www.facebook.com/tr?id=PIXEL_ID&ev=PageView&noscript=1"/></noscript>
{% endif %}
Snippets here come from EXECUTABLE templates, so a static check refuses the two mistakes this section prevents:
allow_enhanced_conversions
on a
G-
tag, and a
noscript
Meta pixel not behind a server-verified consent condition.
环境变量:
NEXT_PUBLIC_META_PIXEL_ID
(数字字符串)
基础代码(受同意控制):
javascript
!function(f,b,e,v,n,t,s){if(f.fbq)return;n=f.fbq=function(){n.callMethod?
n.callMethod.apply(n,arguments):n.queue.push(arguments)};if(!f._fbq)f._fbq=n;
n.push=n;n.loaded=!0;n.version='2.0';n.queue=[];t=b.createElement(e);t.async=!0;
t.src=v;s=b.getElementsByTagName(e)[0];s.parentNode.insertBefore(t,s)}
(window,document,'script','https://connect.facebook.net/en_US/fbevents.js');
fbq('init', 'PIXEL_ID');
fbq('track', 'PageView');
noscript 回退方案——受同意控制,否则省略。 裸露的
<noscript>
Meta 像素会在每个禁用 JS 的页面浏览时触发——没有横幅、没有
fbq
、没有同意——这与本栈“Meta 在获得同意前不渲染”的规则相冲突。因此应由服务端输出,且仅当已经存在可由服务端验证的同意(即服务端在写出 HTML 前读取的、带签名的 HttpOnly Cookie)时才输出。无法在服务端验证同意?请省略它——Meta 的验证不需要它,而且已经触发的请求无法撤回:
html
<!-- server-side: render ONLY when consent is proven -->
{% if consent_cookie_verified and consent.ad_storage == "granted" %}
<noscript><img height="1" width="1" style="display:none"
  src="https://www.facebook.com/tr?id=PIXEL_ID&ev=PageView&noscript=1"/></noscript>
{% endif %}
这里的代码片段来自可执行模板,因此静态检查会拒绝本节防止的两个错误:在
G-
标签上设置
allow_enhanced_conversions
,以及设置不受服务端已验证同意条件保护的
noscript
Meta 像素。

Standard Events

标准事件

Meta defines 17 standard events. The four that carry a SaaS funnel are
CompleteRegistration
,
InitiateCheckout
,
Purchase
and
Subscribe
, and only
Purchase
has mandatory parameters:
value
and
currency
, without which Meta cannot optimise. The full table — every event, its parameters and the traps — is in
references/meta-linkedin.md
→ Standard events.
Meta 定义了 17 个标准事件。其中,承载 SaaS 漏斗的四个事件是
CompleteRegistration
、
InitiateCheckout
、
Purchase
和
Subscribe
,且只有
Purchase
带有强制参数:
value
和
currency
,缺少它们时 Meta 无法进行优化。完整表格——包括每个事件、其参数和陷阱——位于
references/meta-linkedin.md
→ 标准事件(Standard events)。

Deeper Meta and LinkedIn detail

Meta 与 LinkedIn 的更多细节

Read
references/meta-linkedin.md
for the parameter object per event, the firing wrapper, advanced matching (hashed identifiers, and what must never be sent) and CAPI deduplication.
有关每个事件的参数对象、触发 wrapper、高级匹配(经过哈希处理的标识符,以及绝不能发送的内容)和 CAPI 去重,请阅读
references/meta-linkedin.md
。

LinkedIn Insight Tag

LinkedIn Insight Tag

Consent-gated on the same wrapper as the Meta Pixel, env var
NEXT_PUBLIC_LINKEDIN_PARTNER_ID
. Conversions are configured in Campaign Manager rather than in code, so there is nothing to deduplicate and nothing that fails silently in the bundle — but a URL rule on a refreshable page counts every refresh. Setup snippet, the Campaign Manager steps and
lintrk
are in
references/meta-linkedin.md
→ LinkedIn conversion tracking.
与 Meta Pixel 使用同一个 wrapper 进行同意控制,环境变量为
NEXT_PUBLIC_LINKEDIN_PARTNER_ID
。转化在 Campaign Manager 中配置,而不是在代码中配置,因此既无需去重,也不会在 bundle 中静默失败——但在可刷新页面上,URL 规则会计入每次刷新。设置代码片段、Campaign Manager 步骤和
lintrk
位于
references/meta-linkedin.md
→ LinkedIn 转化跟踪(LinkedIn conversion tracking)。

User Identification

用户识别

One rule the body keeps, because getting it wrong corrupts attribution rather than losing it: identify before you track, never after. An event fired for an anonymous id and re-attributed later is two users to every platform that already ingested it. The per-platform strategy, the Mixpanel
alias
vs
identify
distinction and the timing rules are in
references/event-tracking.md
→ User identification.
正文始终强调一条规则,因为处理错误会破坏归因,而不仅仅是丢失归因:先识别,再跟踪,绝不能反过来。 对匿名 id 触发事件、之后再重新归因,会让每个已经摄取该事件的平台都将其视为两个用户。各平台策略、Mixpanel 的
alias
与
identify
区别以及时序规则位于
references/event-tracking.md
→ 用户识别(User identification)。

Event naming

事件命名

Read
references/event-tracking.md
→ Recommended Events and Parameter Rules. GA4 reserves a set of names and silently drops events that collide with them; the reference lists which.
有关 GA4 的推荐事件名称和参数规则,请阅读
references/event-tracking.md
→ 推荐事件(Recommended Events) 与 参数规则(Parameter Rules)。GA4 保留了一组名称,与这些名称冲突的事件会被静默丢弃;参考文档列出了具体名称。

E-commerce

电商

Read
references/event-tracking.md
→ Ecommerce Events for the GA4 item schema and the full purchase / add_to_cart / begin_checkout set.
Deduplication is the part that silently doubles revenue. A purchase fired from both the browser and the server (Conversions API, server-side GA4) must carry the SAME
event_id
/
transaction_id
, or both are counted. Run
fixtures/assert-dedup-contract.mjs
rather than reloading the thank-you page:
pixel-and-capi-carry-one-event-id
and
pixel-and-capi-carry-one-event-name
fail an emitter that shares one field and not the other,
no-purchase-reported-before-the-charge-cleared
fails one that reports from the redirect, and
the-browser-event-is-kept
fails one that drops the pixel.
--self-test
deletes each rule and watches the matching assertion go red.
A purchase is not a browser event, and that decides where it comes from. Every other event on the list is something a person did in a tab, so the browser is its natural source. A purchase is the outcome of a charge that succeeded inside a payment system, and the only thing that knows it succeeded is the provider's webhook. Firing it from the thank-you page means firing it from the one place that has no idea whether the charge cleared — which is why a purchase event exists for every session that reached the page and a refund exists for none of them.
The consequence is an ordering, not a preference:
  1. The webhook handler is the source of truth for the purchase. It writes the entitlement, and the same handler sends the server-side conversion. If the handler is where the money is recorded, it is where the event belongs.
  2. The browser event stays, and it stays subordinate. Keep it for the attribution signals only the browser carries (click ids, the consent state, the session), give it the
    event_id
    the server will reuse, and treat the server as authoritative when they disagree.
  3. Everything the browser loses, it loses closest to the money. Blockers, a mobile browser terminating a tab on redirect, a payment provider's hosted page ending the session before the return leg — each of them removes the last step preferentially, so the browser-only purchase count is biased low by an amount you cannot measure from inside the browser.
Reconcile against the provider rather than the analytics dashboard: the count of succeeded charges in a period is a number the payment system will give you exactly, and it is the only external check on this event that is not itself telemetry. See
stripe-billing
→ Depending on one provider for what else that reconciliation surfaces.
有关 GA4 商品 schema 以及完整的 purchase / add_to_cart / begin_checkout 事件集,请阅读
references/event-tracking.md
→ 电商事件(Ecommerce Events)。
去重才是会悄悄让收入翻倍的部分。 如果 purchase 同时由浏览器和服务端(Conversions API、服务端 GA4)触发,必须携带相同的
event_id
/
transaction_id
,否则两者都会被计入。请运行
fixtures/assert-dedup-contract.mjs
,而不是重新加载感谢页:
pixel-and-capi-carry-one-event-id
和
pixel-and-capi-carry-one-event-name
会让仅共享其中一个字段而未共享另一个字段的 emitter 失败;
no-purchase-reported-before-the-charge-cleared
会让从重定向页面报告事件的实现失败;
the-browser-event-is-kept
会让丢弃像素事件的实现失败。
--self-test
会删除每条规则,并观察对应的断言变红。
购买不是浏览器事件,而这决定了它应从哪里产生。 清单中的其他每个事件都是用户在标签页中完成的操作,因此浏览器是天然的数据来源。购买是支付系统内部成功扣款后的结果,而唯一知道扣款成功的是支付提供商的 webhook。从感谢页触发购买事件,意味着从一个根本不知道扣款是否成功的位置触发它——这正是每个到达该页面的会话都会产生一次 purchase 事件、而退款不会产生对应事件的原因。
由此产生的结论是执行顺序,而不是偏好:
  1. webhook handler 是 purchase 的事实来源。 它写入权益,并由同一个 handler 发送服务端转化。如果 handler 是记录资金的位置,事件就应该属于那里。
  2. 浏览器事件保留,但处于从属地位。 保留它是为了归因浏览器独有的信号(点击 ID、同意状态、会话),为它提供服务端将复用的
    event_id
    ,并在二者不一致时以服务端为准。
  3. 浏览器丢失的一切信息,都会在最接近资金环节的地方丢失。 拦截器、移动浏览器在重定向时终止标签页、支付提供商的托管页面在返回阶段结束会话——每种情况都会优先移除最后一步,因此仅依赖浏览器的 purchase 计数会偏低,而且偏差无法从浏览器内部测量。
应与支付提供商核对,而不是与分析仪表板核对:某周期内成功扣款的次数,是支付系统能够准确给出的数字,也是这个事件唯一不依赖遥测技术的外部校验。有关该核对过程还会发现哪些问题,请参阅
stripe-billing
→ 依赖单一提供商(Depending on one provider)。

Content Security Policy

内容安全策略(Content Security Policy)

Read
references/performance-security.md
→ Content Security Policy for the full directive set per platform.
The trap: a nonce on the gtag loader is not enough — Google injects further scripts at runtime, so
script-src
needs the Google domains as well, and a CSP that passes on the landing page can still break the checkout where the Ads conversion tag loads.
有关各平台完整的指令集,请阅读
references/performance-security.md
→ 内容安全策略(Content Security Policy)。
**陷阱是:**仅给 gtag loader 加 nonce 并不足够——Google 会在运行时注入更多脚本,因此
script-src
还需要包含 Google 域名;而且一个能在落地页上通过的 CSP,仍可能在加载 Ads 转化标签的结账页上失效。

Next.js

Next.js

Read
references/gtag-api.md
→ Next.js / React Integration for the component tree, and
references/performance-security.md
→ Script Loading Strategies for
next/script
strategy choice and SPA route changes.
Two traps that are not obvious from either:
  • NEXT_PUBLIC_*
    is inlined at build time. In a Docker build the variable must exist in the builder stage; injecting it at runtime leaves the literal string in the bundle and every event goes nowhere.
  • An authenticated area and an embed/widget page need different gating — the dashboard already has consent, an embed may have none and must not assume the parent page's.
有关组件树,请阅读
references/gtag-api.md
→ Next.js / React 集成(Next.js / React Integration);有关
next/script
策略选择和 SPA 路由变化,请阅读
references/performance-security.md
→ 脚本加载策略(Script Loading Strategies)。
以下两个陷阱从上述内容中都不明显:
  • NEXT_PUBLIC_*
    是在构建时内联的。在 Docker 构建中,该变量必须存在于 builder stage;如果只在运行时注入,bundle 中会留下该字符串的字面值,导致所有事件都发不出去。
  • 身份验证区域和嵌入/widget 页面需要不同的 gating——dashboard 已经获得同意,而 embed 可能没有任何同意状态,不能假设它继承父页面的同意状态。

UTM Attribution

UTM 归因

Read
references/event-tracking.md
→ UTM Attribution for the capture flow (localStorage through OAuth redirects into the DB), the five-parameter table, and the platform click IDs (
gclid
/
fbclid
/
li_fat_id
) — which the SDKs handle automatically; do not strip them from URLs.

有关捕获流程(从 localStorage 经 OAuth 重定向写入 DB)、五参数表和平台点击 ID(
gclid
/
fbclid
/
li_fat_id
),请阅读
references/event-tracking.md
→ UTM 归因(UTM Attribution)。这些 SDK 会自动处理点击 ID;不要将其从 URL 中移除。

Verification

验证

Read
references/performance-security.md
→ Debug & Testing for the full per-platform checklist (GA4 DebugView, Meta Test Events, LinkedIn tag helper) and the consent round-trip.
The check that catches most of it: open the site in a fresh incognito window, deny consent, and confirm in DevTools → Network that no
_ga
or
_gcl_*
cookie is set and that collect requests carry
gcs=G100
. A tag that fires correctly after consent while ignoring the denied default is the failure this whole skill exists to prevent, and it looks fine in every test that starts by accepting.
有关各平台完整检查清单(GA4 DebugView、Meta Test Events、LinkedIn tag helper)和同意状态往返流程,请阅读
references/performance-security.md
→ 调试与测试(Debug & Testing)。
**最有效的检查:**在全新的无痕窗口中打开站点,拒绝同意,并确认在 DevTools → Network 中没有设置
_ga
或
_gcl_*
Cookie,且 collect 请求带有
gcs=G100
。某个标签在同意后能正确触发,却忽略默认拒绝状态,正是整个 skill 要防止的失败;而且它在每个以接受同意为起点的测试中都看起来正常。

Troubleshooting

故障排除

Read
references/performance-security.md
→ Troubleshooting for the common-issues table (consent ordering, duplicate
PageView
, Advanced/Enhanced Matching timing, the localhost guard) and the debug-mode snippet. The two seen most: consent
default
must run before gtag.js loads, and a purchase without
value
/
currency
cannot be optimised against by either platform.

有关常见问题表(同意顺序、重复
PageView
、Advanced/Enhanced Matching 时序、localhost guard)和调试模式代码片段,请阅读
references/performance-security.md
→ 故障排除(Troubleshooting)。最常见的两个问题:同意
default
必须在 gtag.js 加载前运行;缺少
value
/
currency
的 purchase 无法被任一平台用于优化。

Official documentation

官方文档

Google: Consent Mode v2 · gtag.js · GA4 events. Meta: Pixel · Conversions API. LinkedIn: Insight Tag. Compliance: EU DMA.
Google:Consent Mode v2 · gtag.js · GA4 事件。 Meta:Pixel · Conversions API。 LinkedIn:Insight Tag。 合规:EU DMA。

Deep references

深度参考

The sections above are the integration surface across four platforms; these carry the depth this file deliberately does not. Each opens with its own Load this when line, so the trigger has one home and this table stays an index.
FileRead it when
references/consent-mode.md
consent is the task
references/gtag-api.md
you are calling gtag directly
references/event-tracking.md
you are designing the event schema
references/performance-security.md
the tag costs Lighthouse points or fails CSP
references/meta-linkedin.md
the work is Meta or LinkedIn rather than Google
For the page-speed side of the same problem — what the tag does to LCP and TBT, and what to do about it — see the
frontend-performance
skill in this pack.
上面的章节是四个平台的集成界面;更深入的内容有意不放在本文中。每个文件都以自己的**何时加载(Load this when)**说明开头,让触发条件各有归属,并使此表仅充当索引。
文件阅读时机
references/consent-mode.md
任务是处理同意
references/gtag-api.md
你要直接调用 gtag
references/event-tracking.md
你要设计事件 schema
references/performance-security.md
标签影响 Lighthouse 分数或导致 CSP 失败
references/meta-linkedin.md
工作重点是 Meta 或 LinkedIn,而不是 Google
有关同一问题的页面速度方面——标签对 LCP 和 TBT 有何影响,以及应该如何处理——请参阅本技能包中的
frontend-performance
skill。

Degradation

降级

  • Not Claude Code (Cursor, Codex, the skills CLI, the API container): the pack's
    PreToolUse
    gate does not run. It is the thing that refuses a live key or an irreversible action in this pack; elsewhere those rules are advice you follow by hand.
  • Installed by copy rather than as a plugin: the doctrine arrives,
    hooks/
    does not — same loss, same remedy.
  • No network, or the ad platform's API unreachable: server-side events cannot be verified end to end. Say the dedup check did not run rather than reporting it held; a browser pixel firing proves the browser, never the conversion.
  • 不适用于 Claude Code(Cursor、Codex、skills CLI、API container):本包中的
    PreToolUse
    gate 不会运行。它才是本包中用于阻止实时密钥或不可逆操作的机制;在其他地方,这些规则只是需要手动遵循的建议。
  • 通过复制而非作为插件安装:规范已经安装,
    hooks/
    却不会安装——同样会丢失,也需要采用相同的补救措施。
  • 无网络或广告平台 API 不可达:无法端到端验证服务端事件。应说明去重检查未运行,而不是声称检查通过;浏览器像素触发只能证明浏览器端正常,绝不能证明转化已完成。

Triggers

触发词

More phrases that route here: "ad tracking", "fbq", "google ads", "gtag", "cookie consent", "enhanced conversions", "retargeting", "purchase event", "gclid", "UTM", "attribution", "согласие на куки", "аналитика рекламы".
更多会路由到这里的短语:“广告跟踪(ad tracking)”、“fbq”、“Google Ads(google ads)”、“gtag”、“Cookie 同意(cookie consent)”、“增强型转化(enhanced conversions)”、“再营销(retargeting)”、“购买事件(purchase event)”、“gclid”、“UTM”、“归因(attribution)”、“Cookie 同意(согласие на куки)”、“广告分析(аналитика рекламы)”。