@friedbotstudio/create-baseline 0.1.0 → 0.2.1
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/README.md +5 -0
- package/bin/cli.js +8 -2
- package/obj/template/.claude/skills/audit-baseline/audit.sh +11 -5
- package/obj/template/.claude/skills/google-analytics/SKILL.md +129 -0
- package/obj/template/.claude/skills/google-analytics/references/audiences.md +389 -0
- package/obj/template/.claude/skills/google-analytics/references/bigquery.md +470 -0
- package/obj/template/.claude/skills/google-analytics/references/custom-dimensions.md +355 -0
- package/obj/template/.claude/skills/google-analytics/references/custom-events.md +383 -0
- package/obj/template/.claude/skills/google-analytics/references/data-management.md +416 -0
- package/obj/template/.claude/skills/google-analytics/references/debugview.md +364 -0
- package/obj/template/.claude/skills/google-analytics/references/events-fundamentals.md +398 -0
- package/obj/template/.claude/skills/google-analytics/references/gtag.md +502 -0
- package/obj/template/.claude/skills/google-analytics/references/gtm-integration.md +483 -0
- package/obj/template/.claude/skills/google-analytics/references/measurement-protocol.md +519 -0
- package/obj/template/.claude/skills/google-analytics/references/privacy.md +441 -0
- package/obj/template/.claude/skills/google-analytics/references/recommended-events.md +464 -0
- package/obj/template/.claude/skills/google-analytics/references/reporting.md +397 -0
- package/obj/template/.claude/skills/google-analytics/references/setup.md +344 -0
- package/obj/template/.claude/skills/google-analytics/references/user-tracking.md +417 -0
- package/obj/template/.claude/skills/optimize-seo/SKILL.md +313 -0
- package/obj/template/.claude/skills/optimize-seo/scripts/pagespeed.mjs +197 -0
- package/obj/template/.claude/skills/pagespeed-insights/LICENSE.md +37 -0
- package/obj/template/.claude/skills/pagespeed-insights/SKILL.md +446 -0
- package/obj/template/.claude/skills/pagespeed-insights/reference.md +50 -0
- package/obj/template/CLAUDE.md +3 -3
- package/obj/template/docs/init/seed.md +2 -2
- package/obj/template/manifest.json +27 -6
- package/package.json +7 -2
- package/src/CLAUDE.template.md +3 -3
- package/src/cli/install.js +14 -4
- package/src/seed.template.md +2 -2
- package/obj/template/.claude/hooks/lib/__pycache__/resume_writer.cpython-314.pyc +0 -0
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
# GA4 gtag.js Direct Implementation
|
|
2
|
+
|
|
3
|
+
Expert guidance for implementing GA4 using gtag.js directly without Google Tag Manager.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
gtag.js (Google Tag) is the official JavaScript library for implementing GA4 tracking directly on websites. This reference covers installation, gtag commands, event tracking, user properties, and framework integration patterns.
|
|
8
|
+
|
|
9
|
+
## When to Use gtag.js
|
|
10
|
+
|
|
11
|
+
**Choose gtag.js when:**
|
|
12
|
+
- Only need Google products (GA4, Google Ads)
|
|
13
|
+
- Want lightweight implementation
|
|
14
|
+
- Have code access to website
|
|
15
|
+
- No tag management system required
|
|
16
|
+
- Simple tracking needs
|
|
17
|
+
|
|
18
|
+
**Choose GTM instead when:**
|
|
19
|
+
- Need multiple tracking tags
|
|
20
|
+
- Team collaboration required
|
|
21
|
+
- Frequent changes expected
|
|
22
|
+
- Want version control for tags
|
|
23
|
+
|
|
24
|
+
## Installation
|
|
25
|
+
|
|
26
|
+
### Basic Installation
|
|
27
|
+
|
|
28
|
+
Place in `<head>` section, before all other scripts:
|
|
29
|
+
|
|
30
|
+
```html
|
|
31
|
+
<!-- Google tag (gtag.js) -->
|
|
32
|
+
<script async src="https://www.googletagmanager.com/gtag/js?id=G-XXXXXXXXXX"></script>
|
|
33
|
+
<script>
|
|
34
|
+
window.dataLayer = window.dataLayer || [];
|
|
35
|
+
function gtag(){dataLayer.push(arguments);}
|
|
36
|
+
gtag('js', new Date());
|
|
37
|
+
gtag('config', 'G-XXXXXXXXXX');
|
|
38
|
+
</script>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Placement Requirements
|
|
42
|
+
|
|
43
|
+
| Requirement | Description |
|
|
44
|
+
|-------------|-------------|
|
|
45
|
+
| Location | `<head>` section |
|
|
46
|
+
| Position | Before all other scripts |
|
|
47
|
+
| Async | Script loaded asynchronously |
|
|
48
|
+
| Every page | Must be on all pages |
|
|
49
|
+
|
|
50
|
+
## gtag Commands
|
|
51
|
+
|
|
52
|
+
### gtag('js', new Date())
|
|
53
|
+
|
|
54
|
+
Initialises the library with timestamp.
|
|
55
|
+
|
|
56
|
+
```javascript
|
|
57
|
+
gtag('js', new Date());
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**Required:** Yes, must be called first
|
|
61
|
+
|
|
62
|
+
### gtag('config', 'G-ID')
|
|
63
|
+
|
|
64
|
+
Configures GA4 with Measurement ID.
|
|
65
|
+
|
|
66
|
+
**Basic:**
|
|
67
|
+
```javascript
|
|
68
|
+
gtag('config', 'G-XXXXXXXXXX');
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**With Options:**
|
|
72
|
+
```javascript
|
|
73
|
+
gtag('config', 'G-XXXXXXXXXX', {
|
|
74
|
+
'page_title': 'Custom Page Title',
|
|
75
|
+
'page_location': window.location.href,
|
|
76
|
+
'send_page_view': true,
|
|
77
|
+
'allow_google_signals': true,
|
|
78
|
+
'allow_ad_personalization_signals': true,
|
|
79
|
+
'user_id': 'USER_12345'
|
|
80
|
+
});
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### gtag('event', 'name', {params})
|
|
84
|
+
|
|
85
|
+
Sends events to GA4.
|
|
86
|
+
|
|
87
|
+
**Simple Event:**
|
|
88
|
+
```javascript
|
|
89
|
+
gtag('event', 'button_click', {
|
|
90
|
+
'button_name': 'Subscribe',
|
|
91
|
+
'button_location': 'header'
|
|
92
|
+
});
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Recommended Event:**
|
|
96
|
+
```javascript
|
|
97
|
+
gtag('event', 'purchase', {
|
|
98
|
+
'transaction_id': 'TXN_12345',
|
|
99
|
+
'value': 99.99,
|
|
100
|
+
'currency': 'USD',
|
|
101
|
+
'items': [{
|
|
102
|
+
'item_id': 'SKU_001',
|
|
103
|
+
'item_name': 'Product Name',
|
|
104
|
+
'price': 99.99,
|
|
105
|
+
'quantity': 1
|
|
106
|
+
}]
|
|
107
|
+
});
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### gtag('set', {properties})
|
|
111
|
+
|
|
112
|
+
Sets user properties and User ID.
|
|
113
|
+
|
|
114
|
+
**User ID:**
|
|
115
|
+
```javascript
|
|
116
|
+
// On login
|
|
117
|
+
gtag('set', 'user_id', 'USER_12345');
|
|
118
|
+
|
|
119
|
+
// On logout - MUST use null, never empty string
|
|
120
|
+
gtag('set', 'user_id', null);
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
**User Properties:**
|
|
124
|
+
```javascript
|
|
125
|
+
gtag('set', 'user_properties', {
|
|
126
|
+
'subscription_tier': 'premium',
|
|
127
|
+
'customer_segment': 'enterprise',
|
|
128
|
+
'account_age_days': 365
|
|
129
|
+
});
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### gtag('consent', 'default/update', {})
|
|
133
|
+
|
|
134
|
+
Manages consent state.
|
|
135
|
+
|
|
136
|
+
**Default (Before Consent):**
|
|
137
|
+
```javascript
|
|
138
|
+
gtag('consent', 'default', {
|
|
139
|
+
'ad_storage': 'denied',
|
|
140
|
+
'analytics_storage': 'denied',
|
|
141
|
+
'ad_user_data': 'denied',
|
|
142
|
+
'ad_personalization': 'denied'
|
|
143
|
+
});
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
**Update (After Consent):**
|
|
147
|
+
```javascript
|
|
148
|
+
gtag('consent', 'update', {
|
|
149
|
+
'ad_storage': 'granted',
|
|
150
|
+
'analytics_storage': 'granted',
|
|
151
|
+
'ad_user_data': 'granted',
|
|
152
|
+
'ad_personalization': 'granted'
|
|
153
|
+
});
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
## Common Implementation Patterns
|
|
157
|
+
|
|
158
|
+
### Button Click Tracking
|
|
159
|
+
|
|
160
|
+
```html
|
|
161
|
+
<button id="subscribe-btn">Subscribe</button>
|
|
162
|
+
|
|
163
|
+
<script>
|
|
164
|
+
document.getElementById('subscribe-btn').addEventListener('click', function() {
|
|
165
|
+
gtag('event', 'button_click', {
|
|
166
|
+
'button_name': 'Subscribe',
|
|
167
|
+
'button_location': 'homepage_hero',
|
|
168
|
+
'button_id': 'subscribe-btn'
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
</script>
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
### Form Submission Tracking
|
|
175
|
+
|
|
176
|
+
```html
|
|
177
|
+
<form id="contact-form">
|
|
178
|
+
<input type="email" name="email" required>
|
|
179
|
+
<button type="submit">Submit</button>
|
|
180
|
+
</form>
|
|
181
|
+
|
|
182
|
+
<script>
|
|
183
|
+
document.getElementById('contact-form').addEventListener('submit', function(e) {
|
|
184
|
+
gtag('event', 'form_submit', {
|
|
185
|
+
'form_name': 'Contact Form',
|
|
186
|
+
'form_id': 'contact-form',
|
|
187
|
+
'form_destination': '/thank-you'
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
</script>
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### Video Tracking
|
|
194
|
+
|
|
195
|
+
```javascript
|
|
196
|
+
// Video start
|
|
197
|
+
gtag('event', 'video_start', {
|
|
198
|
+
'video_title': 'Getting Started Guide',
|
|
199
|
+
'video_id': 'VID_001',
|
|
200
|
+
'video_duration': 180
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// Video progress
|
|
204
|
+
gtag('event', 'video_progress', {
|
|
205
|
+
'video_title': 'Getting Started Guide',
|
|
206
|
+
'video_id': 'VID_001',
|
|
207
|
+
'video_percent': 50
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
// Video complete
|
|
211
|
+
gtag('event', 'video_complete', {
|
|
212
|
+
'video_title': 'Getting Started Guide',
|
|
213
|
+
'video_id': 'VID_001'
|
|
214
|
+
});
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Ecommerce Purchase
|
|
218
|
+
|
|
219
|
+
```javascript
|
|
220
|
+
gtag('event', 'purchase', {
|
|
221
|
+
'transaction_id': 'TXN_' + Date.now(),
|
|
222
|
+
'value': 149.99,
|
|
223
|
+
'currency': 'USD',
|
|
224
|
+
'tax': 10.00,
|
|
225
|
+
'shipping': 5.99,
|
|
226
|
+
'coupon': 'SUMMER20',
|
|
227
|
+
'items': [
|
|
228
|
+
{
|
|
229
|
+
'item_id': 'SKU_001',
|
|
230
|
+
'item_name': 'Premium Widget',
|
|
231
|
+
'item_brand': 'Acme',
|
|
232
|
+
'item_category': 'Electronics',
|
|
233
|
+
'item_variant': 'Blue',
|
|
234
|
+
'price': 149.99,
|
|
235
|
+
'quantity': 1
|
|
236
|
+
}
|
|
237
|
+
]
|
|
238
|
+
});
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### User Authentication
|
|
242
|
+
|
|
243
|
+
```javascript
|
|
244
|
+
// On login
|
|
245
|
+
function handleLogin(userId, method) {
|
|
246
|
+
// Set User ID
|
|
247
|
+
gtag('set', 'user_id', userId);
|
|
248
|
+
|
|
249
|
+
// Send login event
|
|
250
|
+
gtag('event', 'login', {
|
|
251
|
+
'method': method
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
// Set user properties
|
|
255
|
+
gtag('set', 'user_properties', {
|
|
256
|
+
'account_type': 'registered',
|
|
257
|
+
'login_method': method
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// On logout
|
|
262
|
+
function handleLogout() {
|
|
263
|
+
// Clear User ID
|
|
264
|
+
gtag('set', 'user_id', null);
|
|
265
|
+
|
|
266
|
+
// Optional: track logout
|
|
267
|
+
gtag('event', 'logout');
|
|
268
|
+
}
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
## Single Page Applications
|
|
272
|
+
|
|
273
|
+
### Manual Page View Tracking
|
|
274
|
+
|
|
275
|
+
SPAs don't trigger traditional page loads. Track manually:
|
|
276
|
+
|
|
277
|
+
```javascript
|
|
278
|
+
// Call on route change
|
|
279
|
+
function trackPageView(path, title) {
|
|
280
|
+
gtag('config', 'G-XXXXXXXXXX', {
|
|
281
|
+
'page_path': path,
|
|
282
|
+
'page_title': title
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// Example with router
|
|
287
|
+
router.afterEach((to) => {
|
|
288
|
+
trackPageView(to.path, to.meta.title);
|
|
289
|
+
});
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### Disable Automatic Page View
|
|
293
|
+
|
|
294
|
+
If handling manually:
|
|
295
|
+
|
|
296
|
+
```javascript
|
|
297
|
+
gtag('config', 'G-XXXXXXXXXX', {
|
|
298
|
+
'send_page_view': false
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
// Then send manually
|
|
302
|
+
gtag('event', 'page_view', {
|
|
303
|
+
'page_title': 'Custom Title',
|
|
304
|
+
'page_location': window.location.href,
|
|
305
|
+
'page_path': '/custom-path'
|
|
306
|
+
});
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
## Framework Integration
|
|
310
|
+
|
|
311
|
+
### React / Next.js
|
|
312
|
+
|
|
313
|
+
```javascript
|
|
314
|
+
// lib/gtag.js
|
|
315
|
+
export const GA_MEASUREMENT_ID = 'G-XXXXXXXXXX';
|
|
316
|
+
|
|
317
|
+
export const pageview = (url) => {
|
|
318
|
+
window.gtag('config', GA_MEASUREMENT_ID, {
|
|
319
|
+
page_path: url,
|
|
320
|
+
});
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
export const event = (action, params) => {
|
|
324
|
+
window.gtag('event', action, params);
|
|
325
|
+
};
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
```javascript
|
|
329
|
+
// In _app.js or layout
|
|
330
|
+
import { useEffect } from 'react';
|
|
331
|
+
import { useRouter } from 'next/router';
|
|
332
|
+
import * as gtag from '../lib/gtag';
|
|
333
|
+
|
|
334
|
+
export default function App({ Component, pageProps }) {
|
|
335
|
+
const router = useRouter();
|
|
336
|
+
|
|
337
|
+
useEffect(() => {
|
|
338
|
+
const handleRouteChange = (url) => {
|
|
339
|
+
gtag.pageview(url);
|
|
340
|
+
};
|
|
341
|
+
router.events.on('routeChangeComplete', handleRouteChange);
|
|
342
|
+
return () => {
|
|
343
|
+
router.events.off('routeChangeComplete', handleRouteChange);
|
|
344
|
+
};
|
|
345
|
+
}, [router.events]);
|
|
346
|
+
|
|
347
|
+
return <Component {...pageProps} />;
|
|
348
|
+
}
|
|
349
|
+
```
|
|
350
|
+
|
|
351
|
+
### Vue.js
|
|
352
|
+
|
|
353
|
+
```javascript
|
|
354
|
+
// plugins/gtag.js
|
|
355
|
+
export default {
|
|
356
|
+
install(app) {
|
|
357
|
+
app.config.globalProperties.$gtag = (action, params) => {
|
|
358
|
+
window.gtag('event', action, params);
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
};
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
```javascript
|
|
365
|
+
// In router
|
|
366
|
+
router.afterEach((to, from) => {
|
|
367
|
+
gtag('config', 'G-XXXXXXXXXX', {
|
|
368
|
+
page_path: to.fullPath,
|
|
369
|
+
page_title: to.meta.title
|
|
370
|
+
});
|
|
371
|
+
});
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### Angular
|
|
375
|
+
|
|
376
|
+
```typescript
|
|
377
|
+
// analytics.service.ts
|
|
378
|
+
import { Injectable } from '@angular/core';
|
|
379
|
+
|
|
380
|
+
declare let gtag: Function;
|
|
381
|
+
|
|
382
|
+
@Injectable({
|
|
383
|
+
providedIn: 'root'
|
|
384
|
+
})
|
|
385
|
+
export class AnalyticsService {
|
|
386
|
+
event(action: string, params: object) {
|
|
387
|
+
gtag('event', action, params);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
pageView(path: string, title: string) {
|
|
391
|
+
gtag('config', 'G-XXXXXXXXXX', {
|
|
392
|
+
page_path: path,
|
|
393
|
+
page_title: title
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
## Multiple Properties
|
|
400
|
+
|
|
401
|
+
Track to multiple GA4 properties:
|
|
402
|
+
|
|
403
|
+
```javascript
|
|
404
|
+
// Configure both
|
|
405
|
+
gtag('config', 'G-XXXXXXXXXX'); // Property 1
|
|
406
|
+
gtag('config', 'G-YYYYYYYYYY'); // Property 2
|
|
407
|
+
|
|
408
|
+
// Events sent to both automatically
|
|
409
|
+
gtag('event', 'purchase', {
|
|
410
|
+
'value': 99.99,
|
|
411
|
+
'currency': 'USD'
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
// Send to specific property only
|
|
415
|
+
gtag('event', 'purchase', {
|
|
416
|
+
'send_to': 'G-XXXXXXXXXX',
|
|
417
|
+
'value': 99.99,
|
|
418
|
+
'currency': 'USD'
|
|
419
|
+
});
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
## Debug Mode
|
|
423
|
+
|
|
424
|
+
Enable debug mode for testing:
|
|
425
|
+
|
|
426
|
+
```javascript
|
|
427
|
+
gtag('config', 'G-XXXXXXXXXX', {
|
|
428
|
+
'debug_mode': true
|
|
429
|
+
});
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
**Important:** Remove for production.
|
|
433
|
+
|
|
434
|
+
## Common Pitfalls
|
|
435
|
+
|
|
436
|
+
### 1. gtag Called Before Initialisation
|
|
437
|
+
|
|
438
|
+
**Problem:** Events called before gtag snippet
|
|
439
|
+
**Solution:** Ensure snippet loads first in `<head>`
|
|
440
|
+
|
|
441
|
+
### 2. Multiple gtag Snippets
|
|
442
|
+
|
|
443
|
+
**Problem:** Duplicate events
|
|
444
|
+
**Solution:** Single snippet, multiple config calls
|
|
445
|
+
|
|
446
|
+
### 3. Empty String for User ID
|
|
447
|
+
|
|
448
|
+
**Problem:** User ID not cleared properly
|
|
449
|
+
**Solution:** Use `null`, never empty string `""`
|
|
450
|
+
|
|
451
|
+
### 4. Missing Currency
|
|
452
|
+
|
|
453
|
+
**Problem:** Monetary events without currency
|
|
454
|
+
**Solution:** Always include `currency` with `value`
|
|
455
|
+
|
|
456
|
+
### 5. Unregistered Parameters
|
|
457
|
+
|
|
458
|
+
**Problem:** Parameters not in reports
|
|
459
|
+
**Solution:** Register as custom dimensions
|
|
460
|
+
|
|
461
|
+
## Critical Rules
|
|
462
|
+
|
|
463
|
+
| Rule | Value |
|
|
464
|
+
|------|-------|
|
|
465
|
+
| Event name max | 40 characters |
|
|
466
|
+
| Parameter name max | 40 characters |
|
|
467
|
+
| Parameter value max | 100 characters |
|
|
468
|
+
| Parameters per event | 25 |
|
|
469
|
+
| Event names | snake_case |
|
|
470
|
+
|
|
471
|
+
## Quick Reference
|
|
472
|
+
|
|
473
|
+
### Initialise
|
|
474
|
+
|
|
475
|
+
```javascript
|
|
476
|
+
gtag('js', new Date());
|
|
477
|
+
gtag('config', 'G-XXXXXXXXXX');
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
### Send Event
|
|
481
|
+
|
|
482
|
+
```javascript
|
|
483
|
+
gtag('event', 'event_name', {params});
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
### Set User ID
|
|
487
|
+
|
|
488
|
+
```javascript
|
|
489
|
+
gtag('set', 'user_id', 'USER_123');
|
|
490
|
+
```
|
|
491
|
+
|
|
492
|
+
### Set User Properties
|
|
493
|
+
|
|
494
|
+
```javascript
|
|
495
|
+
gtag('set', 'user_properties', {key: 'value'});
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
### Clear User ID
|
|
499
|
+
|
|
500
|
+
```javascript
|
|
501
|
+
gtag('set', 'user_id', null);
|
|
502
|
+
```
|