@ssheleg/sheleg-dev 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +172 -0
- package/LICENSE +21 -0
- package/README.md +132 -0
- package/SECURITY.md +63 -0
- package/bin/sheleg-dev.js +99 -0
- package/package.json +45 -0
- package/plugins/sheleg-dev/.claude-plugin/plugin.json +28 -0
- package/plugins/sheleg-dev/skills/ad-tracking/SKILL.md +442 -0
- package/plugins/sheleg-dev/skills/ad-tracking/references/consent-mode.md +257 -0
- package/plugins/sheleg-dev/skills/ad-tracking/references/event-tracking.md +201 -0
- package/plugins/sheleg-dev/skills/ad-tracking/references/gtag-api.md +389 -0
- package/plugins/sheleg-dev/skills/ad-tracking/references/meta-linkedin.md +103 -0
- package/plugins/sheleg-dev/skills/ad-tracking/references/performance-security.md +288 -0
- package/plugins/sheleg-dev/skills/crypto-payments/SKILL.md +384 -0
- package/plugins/sheleg-dev/skills/crypto-payments/references/heleket-provider.md +1357 -0
- package/plugins/sheleg-dev/skills/frontend-performance/SKILL.md +147 -0
- package/plugins/sheleg-dev/skills/frontend-performance/references/accessibility-perf.md +172 -0
- package/plugins/sheleg-dev/skills/frontend-performance/references/css-performance.md +180 -0
- package/plugins/sheleg-dev/skills/frontend-performance/references/nextjs.md +271 -0
- package/plugins/sheleg-dev/skills/google-auth/SKILL.md +275 -0
- package/plugins/sheleg-dev/skills/google-auth/references/adc-and-service-accounts.md +464 -0
- package/plugins/sheleg-dev/skills/google-auth/references/oauth2-web-server.md +615 -0
- package/plugins/sheleg-dev/skills/google-auth/references/sign-in-with-google.md +593 -0
- package/plugins/sheleg-dev/skills/google-auth/references/workload-identity.md +376 -0
- package/plugins/sheleg-dev/skills/google-signin/SKILL.md +121 -0
- package/plugins/sheleg-dev/skills/google-signin/references/full-guide.md +421 -0
- package/plugins/sheleg-dev/skills/stripe-billing/SKILL.md +458 -0
- package/plugins/sheleg-dev/skills/stripe-billing/references/price-integrity.md +201 -0
- package/plugins/sheleg-dev/skills/stripe-billing/references/stripe-agent-toolchain.md +173 -0
- package/plugins/sheleg-dev/skills/stripe-billing/references/subscription-lifecycle.md +294 -0
- package/plugins/sheleg-dev/skills/stripe-billing/references/testing-and-local-dev.md +214 -0
- package/plugins/sheleg-dev/skills/stripe-billing/references/webhook-events.md +211 -0
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ad-tracking
|
|
3
|
+
description: >-
|
|
4
|
+
Use when setting up or modifying ad pixel integration, conversion tracking, consent
|
|
5
|
+
management, purchase event tracking, retargeting audiences, or auditing an existing
|
|
6
|
+
advertising analytics stack. Covers Google Analytics 4, Google Ads, Meta (Facebook) Pixel and
|
|
7
|
+
LinkedIn Insight Tag in web applications: Consent Mode v2, standard events, e-commerce
|
|
8
|
+
tracking, advanced matching, Enhanced Conversions, CSP configuration, user identification,
|
|
9
|
+
cross-device tracking, and GDPR/DMA compliance. Triggers - "ad tracking", "conversion
|
|
10
|
+
tracking", "meta pixel", "fbq", "google ads", "GA4", "gtag", "consent mode", "cookie consent",
|
|
11
|
+
"enhanced conversions", "retargeting", "purchase event", "CAPI", "gclid", "UTM",
|
|
12
|
+
"attribution", "отслеживание конверсий", "пиксель Meta", "согласие на куки", "ретаргетинг",
|
|
13
|
+
"аналитика рекламы". Not for running the ad campaigns themselves.
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# Advertising Analytics & Conversion Tracking Integration
|
|
17
|
+
|
|
18
|
+
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.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## What this skill covers
|
|
23
|
+
|
|
24
|
+
GA4, Google Ads, Meta Pixel and LinkedIn Insight Tag, all behind Consent Mode
|
|
25
|
+
v2. The body carries the minimal setup and the traps; the tables, schemas and
|
|
26
|
+
per-framework wiring live in `references/`, listed at the bottom.
|
|
27
|
+
## Architecture Overview
|
|
28
|
+
|
|
29
|
+
A modern advertising analytics stack consists of multiple pixels/tags that must all respect the same consent state. The architecture follows this pattern:
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
┌──────────────────────────────────────────────────────────┐
|
|
33
|
+
│ CONSENT LAYER │
|
|
34
|
+
│ Cookie Consent Banner → localStorage → consent state │
|
|
35
|
+
│ All pixels gated by the same consent storage key │
|
|
36
|
+
└────────────────────┬─────────────────────────────────────┘
|
|
37
|
+
│ granted / denied
|
|
38
|
+
┌───────────────┼──────────────────┬──────────────────┐
|
|
39
|
+
▼ ▼ ▼ ▼
|
|
40
|
+
┌─────────┐ ┌────────────┐ ┌──────────────┐ ┌────────────┐
|
|
41
|
+
│ GA4 + │ │ Meta │ │ LinkedIn │ │ Mixpanel │
|
|
42
|
+
│ Google │ │ Pixel │ │ Insight │ │ (product │
|
|
43
|
+
│ Ads │ │ (fbq) │ │ Tag │ │ analytics)│
|
|
44
|
+
└─────────┘ └────────────┘ └──────────────┘ └────────────┘
|
|
45
|
+
│ │ │ │
|
|
46
|
+
▼ ▼ ▼ ▼
|
|
47
|
+
Consent Consent-gated Consent-gated Consent-gated
|
|
48
|
+
Mode v2 (render only (render only (opt_in /
|
|
49
|
+
(built-in) when granted) when granted) opt_out)
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
**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.
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Consent
|
|
57
|
+
|
|
58
|
+
Read `references/consent-mode.md` for all seven consent signals, Advanced vs
|
|
59
|
+
Basic mode, region-specific defaults, granular consent and GTM wiring. Consent
|
|
60
|
+
Mode v2 is **mandatory** in the EU/EEA since March 2024.
|
|
61
|
+
|
|
62
|
+
**The order is the whole thing, and getting it wrong is invisible:**
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
1. dataLayer init + gtag() stub
|
|
66
|
+
2. gtag('consent', 'default', { …'denied', wait_for_update: 500 })
|
|
67
|
+
3. restore a stored decision → gtag('consent', 'update', …)
|
|
68
|
+
4. load gtag.js (async)
|
|
69
|
+
5. gtag('js', new Date()) + gtag('config', TAG_ID)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Steps 1–3 must run **synchronously before** gtag.js loads. A page that sets
|
|
73
|
+
defaults after the script has loaded looks correct in every test that begins by
|
|
74
|
+
accepting consent, and loses the denied population entirely.
|
|
75
|
+
|
|
76
|
+
**Denied is not off.** With Advanced mode Google still receives cookieless
|
|
77
|
+
pings and models conversions from them — roughly two thirds of the otherwise
|
|
78
|
+
lost data. Blocking the script instead throws that away.
|
|
79
|
+
## Google Analytics 4
|
|
80
|
+
|
|
81
|
+
### Setup
|
|
82
|
+
|
|
83
|
+
**Env var:** `NEXT_PUBLIC_GA_MEASUREMENT_ID` (format: `G-XXXXXXXXXX`)
|
|
84
|
+
|
|
85
|
+
**Script loading:**
|
|
86
|
+
|
|
87
|
+
```html
|
|
88
|
+
<!-- 1. Consent defaults (MUST be first) -->
|
|
89
|
+
<script>
|
|
90
|
+
window.dataLayer = window.dataLayer || [];
|
|
91
|
+
function gtag(){dataLayer.push(arguments);}
|
|
92
|
+
gtag('consent', 'default', {
|
|
93
|
+
ad_storage: 'denied', ad_user_data: 'denied',
|
|
94
|
+
ad_personalization: 'denied', analytics_storage: 'denied',
|
|
95
|
+
wait_for_update: 500
|
|
96
|
+
});
|
|
97
|
+
// Restore prior consent
|
|
98
|
+
try {
|
|
99
|
+
if (localStorage.getItem('consent_key') === 'granted') {
|
|
100
|
+
gtag('consent', 'update', {
|
|
101
|
+
ad_storage: 'granted', ad_user_data: 'granted',
|
|
102
|
+
ad_personalization: 'granted', analytics_storage: 'granted'
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
} catch(e) {}
|
|
106
|
+
</script>
|
|
107
|
+
|
|
108
|
+
<!-- 2. Load gtag.js -->
|
|
109
|
+
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
|
|
110
|
+
|
|
111
|
+
<!-- 3. Configure -->
|
|
112
|
+
<script>
|
|
113
|
+
gtag('js', new Date());
|
|
114
|
+
gtag('config', 'G-XXXXXXXXXX', { allow_enhanced_conversions: true });
|
|
115
|
+
</script>
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Commands and events
|
|
119
|
+
|
|
120
|
+
Read `references/gtag-api.md` → **Commands** for `config` / `event` / `set` /
|
|
121
|
+
`get` / `consent` with parameter scope and precedence, and
|
|
122
|
+
`references/event-tracking.md` → **Recommended Events** for the GA4 names
|
|
123
|
+
Google Ads can import (`sign_up`, `login`, `begin_checkout`, `purchase`,
|
|
124
|
+
`generate_lead`, `view_item`) with their required parameters.
|
|
125
|
+
|
|
126
|
+
**SPA page views are not automatic when you send them yourself.** Enhanced
|
|
127
|
+
measurement fires `page_view` on History API changes; adding a manual one on
|
|
128
|
+
route change double-counts every navigation. Send manual page views only for
|
|
129
|
+
hash routing.
|
|
130
|
+
### SPA Page Tracking
|
|
131
|
+
|
|
132
|
+
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.
|
|
133
|
+
|
|
134
|
+
**Docs:**
|
|
135
|
+
- [Set up the Google tag](https://developers.google.com/tag-platform/gtagjs)
|
|
136
|
+
- [gtag.js API reference](https://developers.google.com/tag-platform/gtagjs/reference)
|
|
137
|
+
- [Recommended events](https://developers.google.com/tag-platform/gtagjs/reference/events)
|
|
138
|
+
- [Event parameters](https://developers.google.com/tag-platform/gtagjs/reference/parameters)
|
|
139
|
+
- [Data routing & groups](https://developers.google.com/tag-platform/gtagjs/routing)
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Google Ads Conversion Tracking
|
|
144
|
+
|
|
145
|
+
### Architecture
|
|
146
|
+
|
|
147
|
+
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:
|
|
148
|
+
|
|
149
|
+
```javascript
|
|
150
|
+
gtag('config', 'G-XXXXXXXXXX'); // GA4
|
|
151
|
+
gtag('config', 'AW-XXXXXXXXX'); // Google Ads
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Both share consent state, first-party cookies, and the dataLayer pipeline.
|
|
155
|
+
|
|
156
|
+
### Enhanced Conversions
|
|
157
|
+
|
|
158
|
+
Enhanced Conversions improve attribution by matching hashed user data (email, phone) with Google accounts. Setup:
|
|
159
|
+
|
|
160
|
+
1. Enable in GA4 config: `allow_enhanced_conversions: true`
|
|
161
|
+
2. Set user data before conversion events fire:
|
|
162
|
+
|
|
163
|
+
```javascript
|
|
164
|
+
gtag('set', 'user_data', {
|
|
165
|
+
email: 'user@example.com', // Google hashes automatically
|
|
166
|
+
phone_number: '+1234567890', // optional
|
|
167
|
+
});
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
3. Enable in Google Ads console: Tools → Conversions → Enhanced conversions → Turn on
|
|
171
|
+
|
|
172
|
+
### user_id (Cross-Device Tracking)
|
|
173
|
+
|
|
174
|
+
Set GA4 `user_id` after authentication to enable cross-device tracking and audience building:
|
|
175
|
+
|
|
176
|
+
```javascript
|
|
177
|
+
gtag('config', 'G-XXXXXXXXXX', {
|
|
178
|
+
user_id: 'USER_123',
|
|
179
|
+
send_page_view: false // prevent duplicate page_view
|
|
180
|
+
});
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### gclid Preservation Through External Redirects
|
|
184
|
+
|
|
185
|
+
The `gclid` from Google Ads auto-tagging is stored in the `_gcl_aw` first-party cookie. This cookie persists across external redirects (e.g. Stripe Checkout → return URL) because it's set on your domain. No additional code needed.
|
|
186
|
+
|
|
187
|
+
### Google Ads console setup
|
|
188
|
+
|
|
189
|
+
Read `references/gtag-api.md` → **Multi-Product Configuration** for wiring one
|
|
190
|
+
gtag loader to both GA4 and Ads. In the Ads console the conversion action must
|
|
191
|
+
be **Primary** and its attribution window set before the first click lands, or
|
|
192
|
+
early conversions are attributed under the old settings and cannot be
|
|
193
|
+
recomputed.
|
|
194
|
+
## Meta (Facebook) Pixel
|
|
195
|
+
|
|
196
|
+
### Setup
|
|
197
|
+
|
|
198
|
+
**Env var:** `NEXT_PUBLIC_META_PIXEL_ID` (numeric string)
|
|
199
|
+
|
|
200
|
+
**Base code (consent-gated):**
|
|
201
|
+
|
|
202
|
+
```javascript
|
|
203
|
+
!function(f,b,e,v,n,t,s){if(f.fbq)return;n=f.fbq=function(){n.callMethod?
|
|
204
|
+
n.callMethod.apply(n,arguments):n.queue.push(arguments)};if(!f._fbq)f._fbq=n;
|
|
205
|
+
n.push=n;n.loaded=!0;n.version='2.0';n.queue=[];t=b.createElement(e);t.async=!0;
|
|
206
|
+
t.src=v;s=b.getElementsByTagName(e)[0];s.parentNode.insertBefore(t,s)}
|
|
207
|
+
(window,document,'script','https://connect.facebook.net/en_US/fbevents.js');
|
|
208
|
+
fbq('init', 'PIXEL_ID');
|
|
209
|
+
fbq('track', 'PageView');
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
**noscript fallback (required for pixel verification):**
|
|
213
|
+
|
|
214
|
+
```html
|
|
215
|
+
<noscript>
|
|
216
|
+
<img height="1" width="1" style="display:none"
|
|
217
|
+
src="https://www.facebook.com/tr?id=PIXEL_ID&ev=PageView&noscript=1"/>
|
|
218
|
+
</noscript>
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Standard Events
|
|
222
|
+
|
|
223
|
+
Meta defines 17 standard events. The most important for SaaS conversion funnels:
|
|
224
|
+
|
|
225
|
+
| Event | When | Parameters | Notes |
|
|
226
|
+
|---|---|---|---|
|
|
227
|
+
| `PageView` | Every page load | (automatic from base code) | Fires from the init script |
|
|
228
|
+
| `CompleteRegistration` | User signs up | `content_name` (method), `status: true` | Separate from GA4 `sign_up` |
|
|
229
|
+
| `InitiateCheckout` | Checkout started | `value`, `currency`, `content_name`, `content_category`, `num_items` | |
|
|
230
|
+
| `Purchase` | Purchase completed | `value` (required), `currency` (required), `content_name`, `content_category`, `content_type`, `contents[]` | `value` + `currency` are **mandatory** for optimisation |
|
|
231
|
+
| `Subscribe` | Recurring subscription | `value`, `currency`, `predicted_ltv` | Distinct from one-time Purchase |
|
|
232
|
+
| `Lead` | Lead form submitted | (optional params) | |
|
|
233
|
+
| `ViewContent` | Key content viewed | `content_name`, `content_category` | |
|
|
234
|
+
| `AddToCart` | Item added to cart | `content_name`, `value`, `currency` | |
|
|
235
|
+
| `Search` | Search performed | `search_string` | |
|
|
236
|
+
|
|
237
|
+
### Deeper Meta and LinkedIn detail
|
|
238
|
+
|
|
239
|
+
Read `references/meta-linkedin.md` for the parameter object per event, the
|
|
240
|
+
firing wrapper, advanced matching (hashed identifiers, and what must never be
|
|
241
|
+
sent) and CAPI deduplication.
|
|
242
|
+
## LinkedIn Insight Tag
|
|
243
|
+
|
|
244
|
+
### Setup
|
|
245
|
+
|
|
246
|
+
**Env var:** `NEXT_PUBLIC_LINKEDIN_PARTNER_ID` (numeric string)
|
|
247
|
+
|
|
248
|
+
The LinkedIn Insight Tag is consent-gated (same pattern as Meta Pixel). It loads the `insight.min.js` script and includes a noscript pixel fallback.
|
|
249
|
+
|
|
250
|
+
```javascript
|
|
251
|
+
_linkedin_partner_id = "PARTNER_ID";
|
|
252
|
+
window._linkedin_data_partner_ids = window._linkedin_data_partner_ids || [];
|
|
253
|
+
window._linkedin_data_partner_ids.push(_linkedin_partner_id);
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### Conversion Tracking
|
|
257
|
+
|
|
258
|
+
LinkedIn conversions are configured in Campaign Manager (not in code). The Insight Tag automatically tracks page views, and you define URL-based conversion rules in the LinkedIn Campaign Manager:
|
|
259
|
+
|
|
260
|
+
1. Campaign Manager → Analyze → Conversion tracking → Create conversion
|
|
261
|
+
2. Define URL rules (e.g. `/thank-you`, `/onboarding`)
|
|
262
|
+
3. LinkedIn Insight Tag automatically matches page loads to conversion rules
|
|
263
|
+
|
|
264
|
+
For event-based conversions, use `window.lintrk('track', { conversion_id: 123456 })`.
|
|
265
|
+
|
|
266
|
+
**Docs:**
|
|
267
|
+
- [LinkedIn Insight Tag setup](https://www.linkedin.com/help/lms/answer/a418880)
|
|
268
|
+
- [LinkedIn conversion tracking](https://www.linkedin.com/help/lms/answer/a423304)
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## User Identification
|
|
273
|
+
|
|
274
|
+
### Cross-Platform Identification Strategy
|
|
275
|
+
|
|
276
|
+
| Platform | Method | When | What it enables |
|
|
277
|
+
|---|---|---|---|
|
|
278
|
+
| GA4 | `user_id` via `gtag('config', TAG_ID, { user_id })` | After login/onboarding | Cross-device tracking, audience building |
|
|
279
|
+
| GA4 | Enhanced Conversions via `gtag('set', 'user_data', { email })` | After login/onboarding | Better conversion attribution |
|
|
280
|
+
| Meta Pixel | Advanced Matching via `fbq('init', PIXEL_ID, { em, fn })` | After login/onboarding | Better conversion attribution, larger custom audiences |
|
|
281
|
+
| Mixpanel | `alias()` + `identify()` | After login/onboarding | Merge anonymous → identified user profiles |
|
|
282
|
+
|
|
283
|
+
**All identification calls should be placed in a single identification component** that runs in the dashboard/authenticated layout. This ensures:
|
|
284
|
+
- User data is sent once per session
|
|
285
|
+
- All platforms receive identification data at the same time
|
|
286
|
+
- Pre-login anonymous events merge into the identified profile
|
|
287
|
+
|
|
288
|
+
### Alias vs Identify (Mixpanel)
|
|
289
|
+
|
|
290
|
+
- `alias(userId, anonymousId)` — creates a permanent link between the anonymous and real user ID. Must be called **exactly once** per user, **before** `identify()`. Use a localStorage flag to prevent duplicate calls.
|
|
291
|
+
- `identify(userId)` — sets the user ID for all subsequent events.
|
|
292
|
+
|
|
293
|
+
### Timing
|
|
294
|
+
|
|
295
|
+
Fire identification in a `useEffect` in the authenticated layout:
|
|
296
|
+
|
|
297
|
+
```typescript
|
|
298
|
+
useEffect(() => {
|
|
299
|
+
// 1. Mixpanel alias + identify
|
|
300
|
+
// 2. GA4 user_id + Enhanced Conversions
|
|
301
|
+
// 3. Meta Advanced Matching
|
|
302
|
+
}, [userId, email, name]);
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
---
|
|
306
|
+
|
|
307
|
+
## Event naming
|
|
308
|
+
|
|
309
|
+
Read `references/event-tracking.md` → **Recommended Events** and **Parameter
|
|
310
|
+
Rules**. GA4 reserves a set of names and silently drops events that collide
|
|
311
|
+
with them; the reference lists which.
|
|
312
|
+
## E-commerce
|
|
313
|
+
|
|
314
|
+
Read `references/event-tracking.md` → **Ecommerce Events** for the GA4 item
|
|
315
|
+
schema and the full purchase / add_to_cart / begin_checkout set.
|
|
316
|
+
|
|
317
|
+
**Deduplication is the part that silently doubles revenue.** A purchase fired
|
|
318
|
+
from both the browser and the server (Conversions API, server-side GA4) must
|
|
319
|
+
carry the SAME `event_id` / `transaction_id`, or both are counted. Test it by
|
|
320
|
+
reloading the thank-you page: a purchase that increments twice is a
|
|
321
|
+
deduplication bug, not a tracking success.
|
|
322
|
+
## Content Security Policy
|
|
323
|
+
|
|
324
|
+
Read `references/performance-security.md` → **Content Security Policy** for the
|
|
325
|
+
full directive set per platform.
|
|
326
|
+
|
|
327
|
+
**The trap:** a nonce on the gtag loader is not enough — Google injects further
|
|
328
|
+
scripts at runtime, so `script-src` needs the Google domains as well, and a CSP
|
|
329
|
+
that passes on the landing page can still break the checkout where the Ads
|
|
330
|
+
conversion tag loads.
|
|
331
|
+
## Next.js
|
|
332
|
+
|
|
333
|
+
Read `references/gtag-api.md` → **Next.js / React Integration** for the
|
|
334
|
+
component tree, and `references/performance-security.md` → **Script Loading
|
|
335
|
+
Strategies** for `next/script` strategy choice and SPA route changes.
|
|
336
|
+
|
|
337
|
+
**Two traps that are not obvious from either:**
|
|
338
|
+
|
|
339
|
+
- `NEXT_PUBLIC_*` is inlined at **build** time. In a Docker build the variable
|
|
340
|
+
must exist in the builder stage; injecting it at runtime leaves the literal
|
|
341
|
+
string in the bundle and every event goes nowhere.
|
|
342
|
+
- An authenticated area and an embed/widget page need different gating — the
|
|
343
|
+
dashboard already has consent, an embed may have none and must not assume
|
|
344
|
+
the parent page's.
|
|
345
|
+
## UTM Attribution
|
|
346
|
+
|
|
347
|
+
### Capture Flow
|
|
348
|
+
|
|
349
|
+
1. `UtmCapture` component reads UTM params from URL on page load → stores in localStorage
|
|
350
|
+
2. On registration (credentials): read UTM from localStorage, send in POST body, save to DB
|
|
351
|
+
3. On registration (OAuth): UTM persists in localStorage through redirect → synced after return via API call
|
|
352
|
+
4. Mixpanel `people.set_once()` stores initial UTM as permanent user properties
|
|
353
|
+
5. Clear localStorage after successful DB sync
|
|
354
|
+
|
|
355
|
+
### UTM Parameters
|
|
356
|
+
|
|
357
|
+
| Parameter | Purpose | Example |
|
|
358
|
+
|---|---|---|
|
|
359
|
+
| `utm_source` | Traffic source | `google`, `facebook`, `newsletter` |
|
|
360
|
+
| `utm_medium` | Marketing medium | `cpc`, `social`, `email` |
|
|
361
|
+
| `utm_campaign` | Campaign name | `spring_sale_2025` |
|
|
362
|
+
| `utm_content` | Ad creative variant | `banner_a`, `text_link` |
|
|
363
|
+
| `utm_term` | Paid search keyword | `ai+chatbot` |
|
|
364
|
+
|
|
365
|
+
### gclid / fbclid / li_fat_id
|
|
366
|
+
|
|
367
|
+
Google, Meta, and LinkedIn append their own click IDs to URLs:
|
|
368
|
+
- `gclid` — Google Ads click ID (stored in `_gcl_aw` cookie by gtag.js automatically)
|
|
369
|
+
- `fbclid` — Meta click ID (stored in `_fbc` cookie by fbevents.js automatically)
|
|
370
|
+
- `li_fat_id` — LinkedIn click ID (handled by Insight Tag)
|
|
371
|
+
|
|
372
|
+
These are handled automatically by the respective SDKs. Do not strip them from URLs.
|
|
373
|
+
|
|
374
|
+
---
|
|
375
|
+
|
|
376
|
+
## Verification
|
|
377
|
+
|
|
378
|
+
Read `references/performance-security.md` → **Debug & Testing** for the full
|
|
379
|
+
per-platform checklist (GA4 DebugView, Meta Test Events, LinkedIn tag helper)
|
|
380
|
+
and the consent round-trip.
|
|
381
|
+
|
|
382
|
+
**The check that catches most of it:** open the site in a fresh incognito
|
|
383
|
+
window, deny consent, and confirm in DevTools → Network that no `_ga` or
|
|
384
|
+
`_gcl_*` cookie is set and that collect requests carry `gcs=G100`. A tag that
|
|
385
|
+
fires correctly *after* consent while ignoring the denied default is the
|
|
386
|
+
failure this whole skill exists to prevent, and it looks fine in every test
|
|
387
|
+
that starts by accepting.
|
|
388
|
+
## Troubleshooting
|
|
389
|
+
|
|
390
|
+
### Common Issues
|
|
391
|
+
|
|
392
|
+
| Problem | Cause | Fix |
|
|
393
|
+
|---|---|---|
|
|
394
|
+
| No events in GA4 | Consent defaults not set before gtag.js loads | Ensure consent `default` call is **before** the script tag |
|
|
395
|
+
| CSP violations in console | Missing domain in CSP header | Add the domain to the correct CSP directive |
|
|
396
|
+
| Meta Pixel not firing | Consent-gated component not rendering | Check localStorage consent value, check CustomEvent listener |
|
|
397
|
+
| Duplicate `PageView` events | Multiple pixel instances or route change listeners | Ensure pixel init code has `if(f.fbq)return;` guard |
|
|
398
|
+
| Purchase events without value | Missing `value`/`currency` params | Both GA4 and Meta **require** these for conversion optimisation |
|
|
399
|
+
| Advanced Matching not working | `fbq('init')` called before pixel SDK loads | Ensure `fbevents.js` is loaded before calling `setFbAdvancedMatching()` |
|
|
400
|
+
| Enhanced Conversions not matching | `user_data` set after conversion event | Call `setEnhancedConversionData()` **before** the conversion event fires |
|
|
401
|
+
| gclid lost after Stripe redirect | — (shouldn't happen) | `_gcl_aw` cookie is first-party, persists across redirects |
|
|
402
|
+
| Events firing on localhost | Missing `isLocal` guard | Add `if (window.location.hostname === 'localhost') return;` |
|
|
403
|
+
|
|
404
|
+
### Debug Mode
|
|
405
|
+
|
|
406
|
+
Enable debug logging for local development:
|
|
407
|
+
|
|
408
|
+
```typescript
|
|
409
|
+
const isLocal = window.location.hostname === "localhost";
|
|
410
|
+
if (isLocal) {
|
|
411
|
+
console.debug("[Analytics]", eventName, params);
|
|
412
|
+
return; // don't fire real events
|
|
413
|
+
}
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
---
|
|
417
|
+
|
|
418
|
+
## Official documentation
|
|
419
|
+
|
|
420
|
+
Google: [Consent Mode v2](https://developers.google.com/tag-platform/security/guides/consent) ·
|
|
421
|
+
[gtag.js](https://developers.google.com/tag-platform/gtagjs/reference) ·
|
|
422
|
+
[GA4 events](https://developers.google.com/analytics/devguides/collection/ga4/reference/events).
|
|
423
|
+
Meta: [Pixel](https://developers.facebook.com/docs/meta-pixel) ·
|
|
424
|
+
[Conversions API](https://developers.facebook.com/docs/marketing-api/conversions-api).
|
|
425
|
+
LinkedIn: [Insight Tag](https://www.linkedin.com/help/lms/answer/a418880).
|
|
426
|
+
Compliance: [EU DMA](https://digital-markets-act.ec.europa.eu/).
|
|
427
|
+
## Deep references
|
|
428
|
+
|
|
429
|
+
The sections above are the integration surface across four platforms. When the
|
|
430
|
+
work is specifically Google's tag — and it usually becomes that — these carry
|
|
431
|
+
the depth this file deliberately does not:
|
|
432
|
+
|
|
433
|
+
| File | Read it when |
|
|
434
|
+
|---|---|
|
|
435
|
+
| [`references/consent-mode.md`](references/consent-mode.md) | **consent is the task**: advanced vs basic mode and what each still sends, all consent types, region-specific defaults, granular consent with its localStorage schema, the update flow, GTM wiring, exactly what tags do when denied, URL passthrough, ads data redaction, and how to verify in Tag Assistant / DevTools / DebugView |
|
|
436
|
+
| [`references/gtag-api.md`](references/gtag-api.md) | you are **calling gtag directly**: every command (`config`, `event`, `set`, `get`, `consent`), tag-ID formats, parameter scope and precedence, `send_to` routing and groups, multi-product configuration, and the Next.js App Router integration including the Docker `NEXT_PUBLIC_*` build-time trap |
|
|
437
|
+
| [`references/event-tracking.md`](references/event-tracking.md) | you are **designing the event schema**: recommended vs custom events, naming rules, parameter limits, the item object, the ecommerce funnel, SaaS/subscription events, and a typed `trackEvent` wrapper |
|
|
438
|
+
| [`references/performance-security.md`](references/performance-security.md) | the tag is **costing you Lighthouse points or failing CSP**: loading strategies and their measured impact, CSP directives with and without a nonce, EU region endpoints, SPA page_view handling, and verification in the network tab |
|
|
439
|
+
| [`references/meta-linkedin.md`](references/meta-linkedin.md) | the work is **Meta or LinkedIn rather than Google**: the parameter object per standard event, the firing wrapper and its consent gate, advanced matching with hashed identifiers and what must never be sent, and deduplication against the Conversions API |
|
|
440
|
+
|
|
441
|
+
For the page-speed side of the same problem — what the tag does to LCP and TBT,
|
|
442
|
+
and what to do about it — see the `frontend-performance` skill in this pack.
|