@jwiedeman/gtm-kit-next 1.0.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 +343 -0
- package/dist/index.cjs +14 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +48 -0
- package/dist/index.d.ts +48 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/package.json +59 -0
package/README.md
ADDED
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
# @react-gtm-kit/next
|
|
2
|
+
|
|
3
|
+
[](https://github.com/jwiedeman/react-gtm-kit/actions/workflows/ci.yml)
|
|
4
|
+
[](https://codecov.io/gh/jwiedeman/react-gtm-kit)
|
|
5
|
+
[](https://www.npmjs.com/package/@react-gtm-kit/next)
|
|
6
|
+
[](https://bundlephobia.com/package/@react-gtm-kit/next)
|
|
7
|
+
[](https://www.typescriptlang.org/)
|
|
8
|
+
[](https://opensource.org/licenses/MIT)
|
|
9
|
+
[](https://nextjs.org/)
|
|
10
|
+
|
|
11
|
+
**Next.js App Router components for Google Tag Manager. Server components ready.**
|
|
12
|
+
|
|
13
|
+
The Next.js adapter for GTM Kit - provides server components and route tracking hooks.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install @react-gtm-kit/core @react-gtm-kit/next
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
yarn add @react-gtm-kit/core @react-gtm-kit/next
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
pnpm add @react-gtm-kit/core @react-gtm-kit/next
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Quick Start
|
|
34
|
+
|
|
35
|
+
### Step 1: Add to Layout
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
// app/layout.tsx
|
|
39
|
+
import { GtmHeadScript, GtmNoScript } from '@react-gtm-kit/next';
|
|
40
|
+
|
|
41
|
+
export default function RootLayout({ children }) {
|
|
42
|
+
return (
|
|
43
|
+
<html>
|
|
44
|
+
<head>
|
|
45
|
+
<GtmHeadScript containers="GTM-XXXXXX" />
|
|
46
|
+
</head>
|
|
47
|
+
<body>
|
|
48
|
+
<GtmNoScript containers="GTM-XXXXXX" />
|
|
49
|
+
{children}
|
|
50
|
+
</body>
|
|
51
|
+
</html>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Step 2: Create Client Provider
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
// app/providers/gtm.tsx
|
|
60
|
+
'use client';
|
|
61
|
+
import { createGtmClient } from '@react-gtm-kit/core';
|
|
62
|
+
import { useTrackPageViews } from '@react-gtm-kit/next';
|
|
63
|
+
|
|
64
|
+
const client = createGtmClient({ containers: 'GTM-XXXXXX' });
|
|
65
|
+
client.init();
|
|
66
|
+
|
|
67
|
+
export function GtmProvider({ children }) {
|
|
68
|
+
useTrackPageViews({ client }); // Auto-tracks route changes
|
|
69
|
+
return children;
|
|
70
|
+
}
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Step 3: Push Events
|
|
74
|
+
|
|
75
|
+
```tsx
|
|
76
|
+
'use client';
|
|
77
|
+
import { pushEvent } from '@react-gtm-kit/core';
|
|
78
|
+
|
|
79
|
+
// In any client component
|
|
80
|
+
function BuyButton({ client }) {
|
|
81
|
+
return <button onClick={() => pushEvent(client, 'purchase', { value: 49.99 })}>Buy Now</button>;
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
## Features
|
|
88
|
+
|
|
89
|
+
| Feature | Description |
|
|
90
|
+
| ---------------------- | ------------------------------------------------------- |
|
|
91
|
+
| **Server Components** | `GtmHeadScript` and `GtmNoScript` are server components |
|
|
92
|
+
| **App Router** | Built for Next.js 13+ App Router |
|
|
93
|
+
| **Auto Page Tracking** | `useTrackPageViews` hook for route changes |
|
|
94
|
+
| **CSP Support** | Nonce support for Content Security Policy |
|
|
95
|
+
| **TypeScript** | Full type definitions included |
|
|
96
|
+
| **Lightweight** | Only what you need for Next.js |
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Server Components
|
|
101
|
+
|
|
102
|
+
### `<GtmHeadScript />`
|
|
103
|
+
|
|
104
|
+
Renders the GTM script tag. Place in your `<head>`.
|
|
105
|
+
|
|
106
|
+
```tsx
|
|
107
|
+
import { GtmHeadScript } from '@react-gtm-kit/next';
|
|
108
|
+
|
|
109
|
+
<GtmHeadScript
|
|
110
|
+
containers="GTM-XXXXXX"
|
|
111
|
+
scriptAttributes={{ nonce: 'your-csp-nonce' }} // Optional
|
|
112
|
+
/>;
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### `<GtmNoScript />`
|
|
116
|
+
|
|
117
|
+
Renders the noscript fallback iframe. Place at the start of `<body>`.
|
|
118
|
+
|
|
119
|
+
```tsx
|
|
120
|
+
import { GtmNoScript } from '@react-gtm-kit/next';
|
|
121
|
+
|
|
122
|
+
<GtmNoScript containers="GTM-XXXXXX" />;
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Client Hooks
|
|
128
|
+
|
|
129
|
+
### `useTrackPageViews()`
|
|
130
|
+
|
|
131
|
+
Automatically tracks page views on route changes.
|
|
132
|
+
|
|
133
|
+
```tsx
|
|
134
|
+
'use client';
|
|
135
|
+
import { useTrackPageViews } from '@react-gtm-kit/next';
|
|
136
|
+
|
|
137
|
+
export function GtmProvider({ children, client }) {
|
|
138
|
+
useTrackPageViews({ client });
|
|
139
|
+
return children;
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## Full Setup Example
|
|
146
|
+
|
|
147
|
+
### 1. Root Layout
|
|
148
|
+
|
|
149
|
+
```tsx
|
|
150
|
+
// app/layout.tsx
|
|
151
|
+
import { GtmHeadScript, GtmNoScript } from '@react-gtm-kit/next';
|
|
152
|
+
import { GtmProvider } from './providers/gtm';
|
|
153
|
+
|
|
154
|
+
export default function RootLayout({ children }) {
|
|
155
|
+
return (
|
|
156
|
+
<html>
|
|
157
|
+
<head>
|
|
158
|
+
<GtmHeadScript containers="GTM-XXXXXX" />
|
|
159
|
+
</head>
|
|
160
|
+
<body>
|
|
161
|
+
<GtmNoScript containers="GTM-XXXXXX" />
|
|
162
|
+
<GtmProvider>{children}</GtmProvider>
|
|
163
|
+
</body>
|
|
164
|
+
</html>
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### 2. GTM Provider
|
|
170
|
+
|
|
171
|
+
```tsx
|
|
172
|
+
// app/providers/gtm.tsx
|
|
173
|
+
'use client';
|
|
174
|
+
import { createGtmClient } from '@react-gtm-kit/core';
|
|
175
|
+
import { useTrackPageViews } from '@react-gtm-kit/next';
|
|
176
|
+
import { createContext, useContext } from 'react';
|
|
177
|
+
|
|
178
|
+
const client = createGtmClient({ containers: 'GTM-XXXXXX' });
|
|
179
|
+
client.init();
|
|
180
|
+
|
|
181
|
+
const GtmContext = createContext(client);
|
|
182
|
+
|
|
183
|
+
export function GtmProvider({ children }) {
|
|
184
|
+
useTrackPageViews({ client });
|
|
185
|
+
return <GtmContext.Provider value={client}>{children}</GtmContext.Provider>;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export function useGtmClient() {
|
|
189
|
+
return useContext(GtmContext);
|
|
190
|
+
}
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
### 3. Use in Components
|
|
194
|
+
|
|
195
|
+
```tsx
|
|
196
|
+
// app/components/BuyButton.tsx
|
|
197
|
+
'use client';
|
|
198
|
+
import { pushEvent } from '@react-gtm-kit/core';
|
|
199
|
+
import { useGtmClient } from '../providers/gtm';
|
|
200
|
+
|
|
201
|
+
export function BuyButton() {
|
|
202
|
+
const client = useGtmClient();
|
|
203
|
+
|
|
204
|
+
return <button onClick={() => pushEvent(client, 'purchase', { value: 49.99 })}>Buy Now</button>;
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## Consent Mode v2 (GDPR)
|
|
211
|
+
|
|
212
|
+
```tsx
|
|
213
|
+
// app/providers/gtm.tsx
|
|
214
|
+
'use client';
|
|
215
|
+
import { createGtmClient, consentPresets } from '@react-gtm-kit/core';
|
|
216
|
+
import { useTrackPageViews } from '@react-gtm-kit/next';
|
|
217
|
+
|
|
218
|
+
const client = createGtmClient({ containers: 'GTM-XXXXXX' });
|
|
219
|
+
|
|
220
|
+
// Set consent defaults BEFORE init
|
|
221
|
+
client.setConsentDefaults(consentPresets.eeaDefault, { region: ['EEA'] });
|
|
222
|
+
client.init();
|
|
223
|
+
|
|
224
|
+
export function GtmProvider({ children }) {
|
|
225
|
+
useTrackPageViews({ client });
|
|
226
|
+
return children;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Export for consent updates
|
|
230
|
+
export { client };
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
```tsx
|
|
234
|
+
// app/components/CookieBanner.tsx
|
|
235
|
+
'use client';
|
|
236
|
+
import { client } from '../providers/gtm';
|
|
237
|
+
import { consentPresets } from '@react-gtm-kit/core';
|
|
238
|
+
|
|
239
|
+
export function CookieBanner() {
|
|
240
|
+
// Accept all tracking
|
|
241
|
+
const acceptAll = () => client.updateConsent(consentPresets.allGranted);
|
|
242
|
+
|
|
243
|
+
// Reject all tracking
|
|
244
|
+
const rejectAll = () => client.updateConsent(consentPresets.eeaDefault);
|
|
245
|
+
|
|
246
|
+
// Analytics only (mixed consent)
|
|
247
|
+
const analyticsOnly = () => client.updateConsent(consentPresets.analyticsOnly);
|
|
248
|
+
|
|
249
|
+
// Partial update - only change specific categories
|
|
250
|
+
const customChoice = () =>
|
|
251
|
+
client.updateConsent({
|
|
252
|
+
analytics_storage: 'granted',
|
|
253
|
+
ad_storage: 'denied'
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
return (
|
|
257
|
+
<div>
|
|
258
|
+
<button onClick={acceptAll}>Accept All</button>
|
|
259
|
+
<button onClick={rejectAll}>Reject All</button>
|
|
260
|
+
<button onClick={analyticsOnly}>Analytics Only</button>
|
|
261
|
+
</div>
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
**Granular Updates** - Update individual categories without affecting others:
|
|
267
|
+
|
|
268
|
+
```tsx
|
|
269
|
+
// User later changes ad preferences from settings page
|
|
270
|
+
client.updateConsent({ ad_storage: 'granted', ad_user_data: 'granted' });
|
|
271
|
+
// analytics_storage and ad_personalization remain unchanged
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
---
|
|
275
|
+
|
|
276
|
+
## CSP (Content Security Policy)
|
|
277
|
+
|
|
278
|
+
For strict CSP configurations, pass a nonce:
|
|
279
|
+
|
|
280
|
+
```tsx
|
|
281
|
+
// app/layout.tsx
|
|
282
|
+
import { headers } from 'next/headers';
|
|
283
|
+
import { GtmHeadScript, GtmNoScript } from '@react-gtm-kit/next';
|
|
284
|
+
|
|
285
|
+
export default function RootLayout({ children }) {
|
|
286
|
+
const nonce = headers().get('x-nonce') || '';
|
|
287
|
+
|
|
288
|
+
return (
|
|
289
|
+
<html>
|
|
290
|
+
<head>
|
|
291
|
+
<GtmHeadScript containers="GTM-XXXXXX" scriptAttributes={{ nonce }} />
|
|
292
|
+
</head>
|
|
293
|
+
<body>
|
|
294
|
+
<GtmNoScript containers="GTM-XXXXXX" />
|
|
295
|
+
{children}
|
|
296
|
+
</body>
|
|
297
|
+
</html>
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
---
|
|
303
|
+
|
|
304
|
+
## Multiple Containers
|
|
305
|
+
|
|
306
|
+
```tsx
|
|
307
|
+
<GtmHeadScript
|
|
308
|
+
containers={[{ id: 'GTM-MAIN' }, { id: 'GTM-ADS', queryParams: { gtm_auth: 'abc', gtm_preview: 'env-1' } }]}
|
|
309
|
+
/>
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
## Pages Router (Legacy)
|
|
315
|
+
|
|
316
|
+
For Next.js Pages Router, use `@react-gtm-kit/react-modern` instead:
|
|
317
|
+
|
|
318
|
+
```tsx
|
|
319
|
+
// pages/_app.tsx
|
|
320
|
+
import { GtmProvider } from '@react-gtm-kit/react-modern';
|
|
321
|
+
|
|
322
|
+
export default function App({ Component, pageProps }) {
|
|
323
|
+
return (
|
|
324
|
+
<GtmProvider config={{ containers: 'GTM-XXXXXX' }}>
|
|
325
|
+
<Component {...pageProps} />
|
|
326
|
+
</GtmProvider>
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
---
|
|
332
|
+
|
|
333
|
+
## Requirements
|
|
334
|
+
|
|
335
|
+
- Next.js 13.4+ (App Router)
|
|
336
|
+
- React 18+
|
|
337
|
+
- `@react-gtm-kit/core` (peer dependency)
|
|
338
|
+
|
|
339
|
+
---
|
|
340
|
+
|
|
341
|
+
## License
|
|
342
|
+
|
|
343
|
+
MIT
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
var navigation = require('next/navigation');
|
|
5
|
+
var gtmKit = require('@jwiedeman/gtm-kit');
|
|
6
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
7
|
+
|
|
8
|
+
var Q="page_view",W=({pagePath:t,url:e,title:n})=>{let r={page_path:t,page_location:e};return n&&(r.page_title=n),r},K=(t,e)=>{var r;let n=typeof window!="undefined"&&((r=window.location)!=null&&r.origin)?window.location.origin:"";return n?`${n}${t}${e}`:`${t}${e}`},J=t=>t&&t.startsWith("#")?t:t?`#${t}`:"",X=({client:t,eventName:e=Q,buildPayload:n=W,includeSearchParams:r=!0,trackHash:i=!1,trackOnMount:l=!0,skipSamePath:o=!0,pushEventFn:f=gtmKit.pushEvent,waitForReady:d=!1,readyPromise:s})=>{if(!t)throw new Error("A GTM client is required to track page views.");let u=navigation.usePathname(),m=navigation.useSearchParams(),p=react.useMemo(()=>!r||!m?"":m.toString(),[r,m]),a=react.useRef(null),h=react.useRef(!1),S=react.useRef(null),k=react.useRef(d?s!=null?s:t.whenReady():null),T=react.useRef(!0);react.useEffect(()=>(T.current=!0,()=>{T.current=!1;}),[]),react.useEffect(()=>{k.current=d?s!=null?s:t.whenReady():null;},[t,s,d]);let D=react.useCallback(g=>{let c=g.filter(w=>w.status==="failed");if(!c.length)return;let v=c.map(w=>w.containerId).join(", ");console.error(`[react-gtm-kit] Failed to load GTM container script(s): ${v}`,c);},[]),E=react.useCallback((g,c,v)=>{if(!g)return;let w=i?J(v):"",C=c?`${g}?${c}`:g,R=`${C}${w}`,G=K(C,w);if(!l&&!h.current){a.current={key:R,pathname:g,search:c,hash:w,pagePath:C,url:G},h.current=!0;return}if(o&&a.current&&a.current.key===R)return;let O=typeof document!="undefined"?document.title:void 0,V=a.current?{pathname:a.current.pathname,search:a.current.search,hash:a.current.hash,pagePath:a.current.pagePath,url:a.current.url}:void 0,F={pathname:g,search:c,hash:w,pagePath:C,url:G,title:O,previous:V},M=()=>{let y=n(F);f(t,e,y),a.current={key:R,pathname:g,search:c,hash:w,pagePath:C,url:G},h.current=!0;};if(d&&k.current){S.current=R,k.current.then(y=>{!T.current||S.current!==R||(D(y),M());}).catch(y=>{!T.current||S.current!==R||(console.error("[react-gtm-kit] Error while waiting for GTM readiness.",y),M());});return}M();},[n,t,e,D,f,o,i,l,d]);react.useEffect(()=>{var c;if(typeof window=="undefined")return;let g=i&&(c=window.location.hash)!=null?c:"";E(u,p,g);},[E,u,p,i]),react.useEffect(()=>{if(!i||typeof window=="undefined")return;let g=()=>{var c;E(u,p,(c=window.location.hash)!=null?c:"");};return window.addEventListener("hashchange",g),()=>{window.removeEventListener("hashchange",g);}},[E,u,p,i]);};var L="https://www.googletagmanager.com",Z=t=>typeof t=="string",I=t=>Z(t)?{id:t}:t,_=t=>Array.isArray(t)?t.map(I):[I(t)],tt=t=>t?Object.entries(t).reduce((e,[n,r])=>(e[n]=String(r),e),{}):{},et=t=>t.endsWith("/")?t.slice(0,-1):t,N=(t,e,n,r,i=gtmKit.DEFAULT_DATA_LAYER_NAME)=>{let l=et(e),o=new URLSearchParams({id:n}),f=tt(r);i!==gtmKit.DEFAULT_DATA_LAYER_NAME&&f.l===void 0&&(f.l=i);for(let[s,u]of Object.entries(f))s!=="id"&&o.set(s,u);return `${l}/${t==="gtm"?"gtm.js":"ns.html"}?${o.toString()}`},$=(t,e,n,r=gtmKit.DEFAULT_DATA_LAYER_NAME)=>N("gtm",t,e,n,r),H=(t,e,n,r=gtmKit.DEFAULT_DATA_LAYER_NAME)=>N("ns",t,e,n,r);var nt=!0,ot=({containers:t,host:e=L,defaultQueryParams:n,scriptAttributes:r,dataLayerName:i=gtmKit.DEFAULT_DATA_LAYER_NAME})=>{let l=_(t);if(!l.length)throw new Error("At least one GTM container is required to render script tags.");return jsxRuntime.jsx(jsxRuntime.Fragment,{children:l.map(o=>{if(!o.id)throw new Error("Container id is required to render GTM script tags.");let f={...n,...o.queryParams},d=$(e,o.id,f,i),{async:s,defer:u,nonce:m,...p}=r!=null?r:{},a={src:d,async:s!=null?s:nt};u!==void 0&&(a.defer=u),m&&(a.nonce=m);for(let[h,S]of Object.entries(p))h==="async"||h==="defer"||h==="nonce"||h==="src"||S!=null&&(a[h]=S);return jsxRuntime.jsx("script",{"data-gtm-container-id":o.id,...a},o.id)})})};var ct=t=>String(t),ut=t=>t.split(";").map(e=>e.trim()).filter(Boolean).reduce((e,n)=>{let[r,i]=n.split(":");if(!r||i===void 0)return e;let l=r.trim().replace(/-([a-z])/g,(f,d)=>d.toUpperCase()),o=i.trim();return !l||!o||(e[l]=o),e},{}),pt=({containers:t,host:e=L,defaultQueryParams:n,iframeAttributes:r,dataLayerName:i=gtmKit.DEFAULT_DATA_LAYER_NAME})=>{let l=_(t);if(!l.length)throw new Error("At least one GTM container is required to render noscript markup.");return jsxRuntime.jsx(jsxRuntime.Fragment,{children:l.map(o=>{if(!o.id)throw new Error("Container id is required to render GTM noscript markup.");let f={...n,...o.queryParams},d=H(e,o.id,f,i),s={...gtmKit.DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES,...r},u={src:d};for(let[m,p]of Object.entries(s))if(m!=="src"&&p!=null){if(m==="style"){typeof p=="string"?u.style=ut(p):typeof p=="object"&&(u.style=p);continue}u[m]=ct(p);}return jsxRuntime.jsx("noscript",{children:jsxRuntime.jsx("iframe",{...u})},o.id)})})};
|
|
9
|
+
|
|
10
|
+
exports.GtmHeadScript = ot;
|
|
11
|
+
exports.GtmNoScript = pt;
|
|
12
|
+
exports.useTrackPageViews = X;
|
|
13
|
+
//# sourceMappingURL=out.js.map
|
|
14
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/route-listener.ts","../src/head-script.tsx","../src/internal/container-helpers.ts","../src/noscript.tsx"],"names":["useCallback","useEffect","useMemo","useRef","usePathname","useSearchParams","pushEvent","DEFAULT_EVENT_NAME","defaultBuildPayload","pagePath","url","title","payload","buildUrl","hash","_a","origin","sanitizeHash","useTrackPageViews","client","eventName","buildPayload","includeSearchParams","trackHash","trackOnMount","skipSamePath","pushEventFn","waitForReady","readyPromise","pathname","searchParams","search","previousRef","hasTrackedRef","pendingKeyRef","readinessRef","isMountedRef","logFailures","states","failed","state","details","handleRouteChange","nextPathname","searchValue","rawHash","normalizedHash","key","previous","pushPayload","error","listener","DEFAULT_DATA_LAYER_NAME","DEFAULT_GTM_HOST","isString","value","normalizeContainer","input","normalizeContainers","containers","toRecord","params","acc","normalizeHost","host","kind","containerId","queryParams","dataLayerName","normalizedHost","buildScriptUrl","buildNoscriptUrl","Fragment","jsx","DEFAULT_ASYNC","GtmHeadScript","defaultQueryParams","scriptAttributes","normalized","container","src","asyncAttr","defer","nonce","restAttributes","scriptProps","attribute","DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES","toStringValue","parseStyle","style","chunk","declaration","property","rawValue","_","char","GtmNoScript","iframeAttributes","attributes","iframeProps"],"mappings":"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","sourcesContent":["'use client';\n\nimport { useCallback, useEffect, useMemo, useRef } from 'react';\nimport { usePathname, useSearchParams } from 'next/navigation';\nimport type { GtmClient, PageViewPayload, ScriptLoadState } from '@jwiedeman/gtm-kit';\nimport { pushEvent } from '@jwiedeman/gtm-kit';\n\nconst DEFAULT_EVENT_NAME = 'page_view';\n\nexport interface RouteLocationSnapshot {\n pathname: string;\n search: string;\n hash: string;\n pagePath: string;\n url: string;\n}\n\nexport interface RouteChangeEventDetails extends RouteLocationSnapshot {\n title?: string;\n previous?: RouteLocationSnapshot;\n}\n\nexport type PageViewPayloadBuilder = (details: RouteChangeEventDetails) => PageViewPayload;\n\nexport interface UseTrackPageViewsOptions {\n client: Pick<GtmClient, 'push' | 'whenReady'>;\n eventName?: string;\n buildPayload?: PageViewPayloadBuilder;\n includeSearchParams?: boolean;\n trackHash?: boolean;\n trackOnMount?: boolean;\n skipSamePath?: boolean;\n pushEventFn?: typeof pushEvent;\n waitForReady?: boolean;\n readyPromise?: Promise<ScriptLoadState[]>;\n}\n\ninterface RouteSnapshot extends RouteLocationSnapshot {\n key: string;\n}\n\nconst defaultBuildPayload: PageViewPayloadBuilder = ({ pagePath, url, title }) => {\n const payload: PageViewPayload = {\n page_path: pagePath,\n page_location: url\n };\n\n if (title) {\n payload.page_title = title;\n }\n\n return payload;\n};\n\nconst buildUrl = (pagePath: string, hash: string): string => {\n const origin = typeof window !== 'undefined' && window.location?.origin ? window.location.origin : '';\n if (!origin) {\n return `${pagePath}${hash}`;\n }\n\n return `${origin}${pagePath}${hash}`;\n};\n\nconst sanitizeHash = (hash: string): string => (hash && hash.startsWith('#') ? hash : hash ? `#${hash}` : '');\n\nexport const useTrackPageViews = ({\n client,\n eventName = DEFAULT_EVENT_NAME,\n buildPayload = defaultBuildPayload,\n includeSearchParams = true,\n trackHash = false,\n trackOnMount = true,\n skipSamePath = true,\n pushEventFn = pushEvent,\n waitForReady = false,\n readyPromise\n}: UseTrackPageViewsOptions): void => {\n if (!client) {\n throw new Error('A GTM client is required to track page views.');\n }\n\n const pathname = usePathname();\n const searchParams = useSearchParams();\n\n const search = useMemo(() => {\n if (!includeSearchParams || !searchParams) {\n return '';\n }\n\n return searchParams.toString();\n }, [includeSearchParams, searchParams]);\n\n const previousRef = useRef<RouteSnapshot | null>(null);\n const hasTrackedRef = useRef(false);\n const pendingKeyRef = useRef<string | null>(null);\n const readinessRef = useRef<Promise<ScriptLoadState[]> | null>(\n waitForReady ? (readyPromise ?? client.whenReady()) : null\n );\n const isMountedRef = useRef(true);\n\n useEffect(() => {\n isMountedRef.current = true;\n return () => {\n isMountedRef.current = false;\n };\n }, []);\n\n useEffect(() => {\n readinessRef.current = waitForReady ? (readyPromise ?? client.whenReady()) : null;\n }, [client, readyPromise, waitForReady]);\n\n const logFailures = useCallback((states: ScriptLoadState[]) => {\n const failed = states.filter((state) => state.status === 'failed');\n if (!failed.length) {\n return;\n }\n\n const details = failed.map((state) => state.containerId).join(', ');\n // eslint-disable-next-line no-console\n console.error(`[react-gtm-kit] Failed to load GTM container script(s): ${details}`, failed);\n }, []);\n\n const handleRouteChange = useCallback(\n (nextPathname: string | null, searchValue: string, rawHash: string) => {\n if (!nextPathname) {\n return;\n }\n\n const normalizedHash = trackHash ? sanitizeHash(rawHash) : '';\n const pagePath = searchValue ? `${nextPathname}?${searchValue}` : nextPathname;\n const key = `${pagePath}${normalizedHash}`;\n const url = buildUrl(pagePath, normalizedHash);\n\n if (!trackOnMount && !hasTrackedRef.current) {\n previousRef.current = {\n key,\n pathname: nextPathname,\n search: searchValue,\n hash: normalizedHash,\n pagePath,\n url\n };\n hasTrackedRef.current = true;\n return;\n }\n\n if (skipSamePath && previousRef.current && previousRef.current.key === key) {\n return;\n }\n\n const title = typeof document !== 'undefined' ? document.title : undefined;\n const previous = previousRef.current\n ? {\n pathname: previousRef.current.pathname,\n search: previousRef.current.search,\n hash: previousRef.current.hash,\n pagePath: previousRef.current.pagePath,\n url: previousRef.current.url\n }\n : undefined;\n\n const details: RouteChangeEventDetails = {\n pathname: nextPathname,\n search: searchValue,\n hash: normalizedHash,\n pagePath,\n url,\n title,\n previous\n };\n\n const pushPayload = (): void => {\n const payload = buildPayload(details);\n pushEventFn(client, eventName, payload);\n\n previousRef.current = {\n key,\n pathname: nextPathname,\n search: searchValue,\n hash: normalizedHash,\n pagePath,\n url\n };\n hasTrackedRef.current = true;\n };\n\n if (waitForReady && readinessRef.current) {\n pendingKeyRef.current = key;\n readinessRef.current\n .then((states) => {\n if (!isMountedRef.current || pendingKeyRef.current !== key) {\n return;\n }\n\n logFailures(states);\n pushPayload();\n })\n .catch((error) => {\n if (!isMountedRef.current || pendingKeyRef.current !== key) {\n return;\n }\n // eslint-disable-next-line no-console\n console.error('[react-gtm-kit] Error while waiting for GTM readiness.', error);\n pushPayload();\n });\n\n return;\n }\n\n pushPayload();\n },\n [buildPayload, client, eventName, logFailures, pushEventFn, skipSamePath, trackHash, trackOnMount, waitForReady]\n );\n\n useEffect(() => {\n if (typeof window === 'undefined') {\n return;\n }\n\n const hash = trackHash ? (window.location.hash ?? '') : '';\n handleRouteChange(pathname, search, hash);\n }, [handleRouteChange, pathname, search, trackHash]);\n\n useEffect(() => {\n if (!trackHash || typeof window === 'undefined') {\n return;\n }\n\n const listener = (): void => {\n handleRouteChange(pathname, search, window.location.hash ?? '');\n };\n\n window.addEventListener('hashchange', listener);\n return () => {\n window.removeEventListener('hashchange', listener);\n };\n }, [handleRouteChange, pathname, search, trackHash]);\n};\n","import type React from 'react';\nimport { DEFAULT_DATA_LAYER_NAME } from '@jwiedeman/gtm-kit';\nimport type { ContainerConfigInput, ScriptAttributes } from '@jwiedeman/gtm-kit';\nimport { buildScriptUrl, DEFAULT_GTM_HOST, normalizeContainers } from './internal/container-helpers';\n\nexport interface GtmHeadScriptProps {\n containers: ContainerConfigInput | ContainerConfigInput[];\n host?: string;\n defaultQueryParams?: Record<string, string | number | boolean>;\n scriptAttributes?: ScriptAttributes;\n dataLayerName?: string;\n}\n\nconst DEFAULT_ASYNC = true;\n\nexport const GtmHeadScript = ({\n containers,\n host = DEFAULT_GTM_HOST,\n defaultQueryParams,\n scriptAttributes,\n dataLayerName = DEFAULT_DATA_LAYER_NAME\n}: GtmHeadScriptProps): React.ReactElement => {\n const normalized = normalizeContainers(containers);\n\n if (!normalized.length) {\n throw new Error('At least one GTM container is required to render script tags.');\n }\n\n return (\n <>\n {normalized.map((container) => {\n if (!container.id) {\n throw new Error('Container id is required to render GTM script tags.');\n }\n\n const params = {\n ...defaultQueryParams,\n ...container.queryParams\n };\n\n const src = buildScriptUrl(host, container.id, params, dataLayerName);\n const { async: asyncAttr, defer, nonce, ...restAttributes } = scriptAttributes ?? {};\n\n const scriptProps: React.ScriptHTMLAttributes<HTMLScriptElement> = {\n src,\n async: asyncAttr ?? DEFAULT_ASYNC\n };\n\n if (defer !== undefined) {\n scriptProps.defer = defer;\n }\n\n if (nonce) {\n scriptProps.nonce = nonce;\n }\n\n for (const [attribute, value] of Object.entries(restAttributes)) {\n if (attribute === 'async' || attribute === 'defer' || attribute === 'nonce' || attribute === 'src') {\n continue;\n }\n\n if (value === undefined || value === null) {\n continue;\n }\n\n (scriptProps as Record<string, unknown>)[attribute] = value;\n }\n\n return <script key={container.id} data-gtm-container-id={container.id} {...scriptProps} />;\n })}\n </>\n );\n};\n","import { DEFAULT_DATA_LAYER_NAME } from '@jwiedeman/gtm-kit';\nimport type { ContainerConfigInput, ContainerDescriptor } from '@jwiedeman/gtm-kit';\n\nexport const DEFAULT_GTM_HOST = 'https://www.googletagmanager.com';\n\nconst isString = (value: unknown): value is string => typeof value === 'string';\n\nexport const normalizeContainer = (input: ContainerConfigInput): ContainerDescriptor => {\n if (isString(input)) {\n return { id: input };\n }\n\n return input;\n};\n\nexport const normalizeContainers = (\n containers: ContainerConfigInput | ContainerConfigInput[]\n): ContainerDescriptor[] => {\n if (Array.isArray(containers)) {\n return containers.map(normalizeContainer);\n }\n\n return [normalizeContainer(containers)];\n};\n\nconst toRecord = (params?: Record<string, string | number | boolean>): Record<string, string> => {\n if (!params) {\n return {};\n }\n\n return Object.entries(params).reduce<Record<string, string>>((acc, [key, value]) => {\n acc[key] = String(value);\n return acc;\n }, {});\n};\n\nconst normalizeHost = (host: string): string => (host.endsWith('/') ? host.slice(0, -1) : host);\n\nconst buildUrl = (\n kind: 'gtm' | 'ns',\n host: string,\n containerId: string,\n queryParams?: Record<string, string | number | boolean>,\n dataLayerName: string = DEFAULT_DATA_LAYER_NAME\n): string => {\n const normalizedHost = normalizeHost(host);\n const searchParams = new URLSearchParams({ id: containerId });\n\n const params = toRecord(queryParams);\n if (dataLayerName !== DEFAULT_DATA_LAYER_NAME && params.l === undefined) {\n params.l = dataLayerName;\n }\n\n for (const [key, value] of Object.entries(params)) {\n if (key === 'id') {\n continue;\n }\n searchParams.set(key, value);\n }\n\n const suffix = kind === 'gtm' ? 'gtm.js' : 'ns.html';\n return `${normalizedHost}/${suffix}?${searchParams.toString()}`;\n};\n\nexport const buildScriptUrl = (\n host: string,\n containerId: string,\n queryParams?: Record<string, string | number | boolean>,\n dataLayerName: string = DEFAULT_DATA_LAYER_NAME\n): string => buildUrl('gtm', host, containerId, queryParams, dataLayerName);\n\nexport const buildNoscriptUrl = (\n host: string,\n containerId: string,\n queryParams?: Record<string, string | number | boolean>,\n dataLayerName: string = DEFAULT_DATA_LAYER_NAME\n): string => buildUrl('ns', host, containerId, queryParams, dataLayerName);\n","import type React from 'react';\nimport { DEFAULT_DATA_LAYER_NAME } from '@jwiedeman/gtm-kit';\nimport type { ContainerConfigInput } from '@jwiedeman/gtm-kit';\nimport { DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES } from '@jwiedeman/gtm-kit';\nimport { buildNoscriptUrl, DEFAULT_GTM_HOST, normalizeContainers } from './internal/container-helpers';\n\nexport interface GtmNoScriptProps {\n containers: ContainerConfigInput | ContainerConfigInput[];\n host?: string;\n defaultQueryParams?: Record<string, string | number | boolean>;\n iframeAttributes?: Record<string, string | number | boolean>;\n dataLayerName?: string;\n}\n\nconst toStringValue = (value: string | number | boolean): string => String(value);\n\nconst parseStyle = (style: string): React.CSSProperties => {\n return style\n .split(';')\n .map((chunk) => chunk.trim())\n .filter(Boolean)\n .reduce<React.CSSProperties>((acc, declaration) => {\n const [property, rawValue] = declaration.split(':');\n if (!property || rawValue === undefined) {\n return acc;\n }\n\n const key = property.trim().replace(/-([a-z])/g, (_, char: string) => char.toUpperCase());\n const value = rawValue.trim();\n if (!key || !value) {\n return acc;\n }\n\n (acc as Record<string, string>)[key] = value;\n return acc;\n }, {});\n};\n\nexport const GtmNoScript = ({\n containers,\n host = DEFAULT_GTM_HOST,\n defaultQueryParams,\n iframeAttributes,\n dataLayerName = DEFAULT_DATA_LAYER_NAME\n}: GtmNoScriptProps): React.ReactElement => {\n const normalized = normalizeContainers(containers);\n\n if (!normalized.length) {\n throw new Error('At least one GTM container is required to render noscript markup.');\n }\n\n return (\n <>\n {normalized.map((container) => {\n if (!container.id) {\n throw new Error('Container id is required to render GTM noscript markup.');\n }\n\n const params = {\n ...defaultQueryParams,\n ...container.queryParams\n };\n\n const src = buildNoscriptUrl(host, container.id, params, dataLayerName);\n const attributes = {\n ...DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES,\n ...iframeAttributes\n };\n\n const iframeProps: React.IframeHTMLAttributes<HTMLIFrameElement> = {\n src\n };\n\n for (const [attribute, value] of Object.entries(attributes)) {\n if (attribute === 'src') {\n continue;\n }\n\n if (value === undefined || value === null) {\n continue;\n }\n\n if (attribute === 'style') {\n if (typeof value === 'string') {\n iframeProps.style = parseStyle(value);\n } else if (typeof value === 'object') {\n iframeProps.style = value as React.CSSProperties;\n }\n continue;\n }\n\n (iframeProps as Record<string, unknown>)[attribute] = toStringValue(value as string | number | boolean);\n }\n\n return (\n <noscript key={container.id}>\n <iframe {...iframeProps} />\n </noscript>\n );\n })}\n </>\n );\n};\n"]}
|
package/dist/index.d.cts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { PageViewPayload, GtmClient, pushEvent, ScriptLoadState, ContainerConfigInput, ScriptAttributes } from '@jwiedeman/gtm-kit';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
interface RouteLocationSnapshot {
|
|
5
|
+
pathname: string;
|
|
6
|
+
search: string;
|
|
7
|
+
hash: string;
|
|
8
|
+
pagePath: string;
|
|
9
|
+
url: string;
|
|
10
|
+
}
|
|
11
|
+
interface RouteChangeEventDetails extends RouteLocationSnapshot {
|
|
12
|
+
title?: string;
|
|
13
|
+
previous?: RouteLocationSnapshot;
|
|
14
|
+
}
|
|
15
|
+
type PageViewPayloadBuilder = (details: RouteChangeEventDetails) => PageViewPayload;
|
|
16
|
+
interface UseTrackPageViewsOptions {
|
|
17
|
+
client: Pick<GtmClient, 'push' | 'whenReady'>;
|
|
18
|
+
eventName?: string;
|
|
19
|
+
buildPayload?: PageViewPayloadBuilder;
|
|
20
|
+
includeSearchParams?: boolean;
|
|
21
|
+
trackHash?: boolean;
|
|
22
|
+
trackOnMount?: boolean;
|
|
23
|
+
skipSamePath?: boolean;
|
|
24
|
+
pushEventFn?: typeof pushEvent;
|
|
25
|
+
waitForReady?: boolean;
|
|
26
|
+
readyPromise?: Promise<ScriptLoadState[]>;
|
|
27
|
+
}
|
|
28
|
+
declare const useTrackPageViews: ({ client, eventName, buildPayload, includeSearchParams, trackHash, trackOnMount, skipSamePath, pushEventFn, waitForReady, readyPromise }: UseTrackPageViewsOptions) => void;
|
|
29
|
+
|
|
30
|
+
interface GtmHeadScriptProps {
|
|
31
|
+
containers: ContainerConfigInput | ContainerConfigInput[];
|
|
32
|
+
host?: string;
|
|
33
|
+
defaultQueryParams?: Record<string, string | number | boolean>;
|
|
34
|
+
scriptAttributes?: ScriptAttributes;
|
|
35
|
+
dataLayerName?: string;
|
|
36
|
+
}
|
|
37
|
+
declare const GtmHeadScript: ({ containers, host, defaultQueryParams, scriptAttributes, dataLayerName }: GtmHeadScriptProps) => React.ReactElement;
|
|
38
|
+
|
|
39
|
+
interface GtmNoScriptProps {
|
|
40
|
+
containers: ContainerConfigInput | ContainerConfigInput[];
|
|
41
|
+
host?: string;
|
|
42
|
+
defaultQueryParams?: Record<string, string | number | boolean>;
|
|
43
|
+
iframeAttributes?: Record<string, string | number | boolean>;
|
|
44
|
+
dataLayerName?: string;
|
|
45
|
+
}
|
|
46
|
+
declare const GtmNoScript: ({ containers, host, defaultQueryParams, iframeAttributes, dataLayerName }: GtmNoScriptProps) => React.ReactElement;
|
|
47
|
+
|
|
48
|
+
export { GtmHeadScript, GtmHeadScriptProps, GtmNoScript, GtmNoScriptProps, PageViewPayloadBuilder, RouteChangeEventDetails, RouteLocationSnapshot, UseTrackPageViewsOptions, useTrackPageViews };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { PageViewPayload, GtmClient, pushEvent, ScriptLoadState, ContainerConfigInput, ScriptAttributes } from '@jwiedeman/gtm-kit';
|
|
2
|
+
import React from 'react';
|
|
3
|
+
|
|
4
|
+
interface RouteLocationSnapshot {
|
|
5
|
+
pathname: string;
|
|
6
|
+
search: string;
|
|
7
|
+
hash: string;
|
|
8
|
+
pagePath: string;
|
|
9
|
+
url: string;
|
|
10
|
+
}
|
|
11
|
+
interface RouteChangeEventDetails extends RouteLocationSnapshot {
|
|
12
|
+
title?: string;
|
|
13
|
+
previous?: RouteLocationSnapshot;
|
|
14
|
+
}
|
|
15
|
+
type PageViewPayloadBuilder = (details: RouteChangeEventDetails) => PageViewPayload;
|
|
16
|
+
interface UseTrackPageViewsOptions {
|
|
17
|
+
client: Pick<GtmClient, 'push' | 'whenReady'>;
|
|
18
|
+
eventName?: string;
|
|
19
|
+
buildPayload?: PageViewPayloadBuilder;
|
|
20
|
+
includeSearchParams?: boolean;
|
|
21
|
+
trackHash?: boolean;
|
|
22
|
+
trackOnMount?: boolean;
|
|
23
|
+
skipSamePath?: boolean;
|
|
24
|
+
pushEventFn?: typeof pushEvent;
|
|
25
|
+
waitForReady?: boolean;
|
|
26
|
+
readyPromise?: Promise<ScriptLoadState[]>;
|
|
27
|
+
}
|
|
28
|
+
declare const useTrackPageViews: ({ client, eventName, buildPayload, includeSearchParams, trackHash, trackOnMount, skipSamePath, pushEventFn, waitForReady, readyPromise }: UseTrackPageViewsOptions) => void;
|
|
29
|
+
|
|
30
|
+
interface GtmHeadScriptProps {
|
|
31
|
+
containers: ContainerConfigInput | ContainerConfigInput[];
|
|
32
|
+
host?: string;
|
|
33
|
+
defaultQueryParams?: Record<string, string | number | boolean>;
|
|
34
|
+
scriptAttributes?: ScriptAttributes;
|
|
35
|
+
dataLayerName?: string;
|
|
36
|
+
}
|
|
37
|
+
declare const GtmHeadScript: ({ containers, host, defaultQueryParams, scriptAttributes, dataLayerName }: GtmHeadScriptProps) => React.ReactElement;
|
|
38
|
+
|
|
39
|
+
interface GtmNoScriptProps {
|
|
40
|
+
containers: ContainerConfigInput | ContainerConfigInput[];
|
|
41
|
+
host?: string;
|
|
42
|
+
defaultQueryParams?: Record<string, string | number | boolean>;
|
|
43
|
+
iframeAttributes?: Record<string, string | number | boolean>;
|
|
44
|
+
dataLayerName?: string;
|
|
45
|
+
}
|
|
46
|
+
declare const GtmNoScript: ({ containers, host, defaultQueryParams, iframeAttributes, dataLayerName }: GtmNoScriptProps) => React.ReactElement;
|
|
47
|
+
|
|
48
|
+
export { GtmHeadScript, GtmHeadScriptProps, GtmNoScript, GtmNoScriptProps, PageViewPayloadBuilder, RouteChangeEventDetails, RouteLocationSnapshot, UseTrackPageViewsOptions, useTrackPageViews };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { useMemo, useRef, useEffect, useCallback } from 'react';
|
|
2
|
+
import { usePathname, useSearchParams } from 'next/navigation';
|
|
3
|
+
import { pushEvent, DEFAULT_DATA_LAYER_NAME, DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES } from '@jwiedeman/gtm-kit';
|
|
4
|
+
import { jsx, Fragment } from 'react/jsx-runtime';
|
|
5
|
+
|
|
6
|
+
var Q="page_view",W=({pagePath:t,url:e,title:n})=>{let r={page_path:t,page_location:e};return n&&(r.page_title=n),r},K=(t,e)=>{var r;let n=typeof window!="undefined"&&((r=window.location)!=null&&r.origin)?window.location.origin:"";return n?`${n}${t}${e}`:`${t}${e}`},J=t=>t&&t.startsWith("#")?t:t?`#${t}`:"",X=({client:t,eventName:e=Q,buildPayload:n=W,includeSearchParams:r=!0,trackHash:i=!1,trackOnMount:l=!0,skipSamePath:o=!0,pushEventFn:f=pushEvent,waitForReady:d=!1,readyPromise:s})=>{if(!t)throw new Error("A GTM client is required to track page views.");let u=usePathname(),m=useSearchParams(),p=useMemo(()=>!r||!m?"":m.toString(),[r,m]),a=useRef(null),h=useRef(!1),S=useRef(null),k=useRef(d?s!=null?s:t.whenReady():null),T=useRef(!0);useEffect(()=>(T.current=!0,()=>{T.current=!1;}),[]),useEffect(()=>{k.current=d?s!=null?s:t.whenReady():null;},[t,s,d]);let D=useCallback(g=>{let c=g.filter(w=>w.status==="failed");if(!c.length)return;let v=c.map(w=>w.containerId).join(", ");console.error(`[react-gtm-kit] Failed to load GTM container script(s): ${v}`,c);},[]),E=useCallback((g,c,v)=>{if(!g)return;let w=i?J(v):"",C=c?`${g}?${c}`:g,R=`${C}${w}`,G=K(C,w);if(!l&&!h.current){a.current={key:R,pathname:g,search:c,hash:w,pagePath:C,url:G},h.current=!0;return}if(o&&a.current&&a.current.key===R)return;let O=typeof document!="undefined"?document.title:void 0,V=a.current?{pathname:a.current.pathname,search:a.current.search,hash:a.current.hash,pagePath:a.current.pagePath,url:a.current.url}:void 0,F={pathname:g,search:c,hash:w,pagePath:C,url:G,title:O,previous:V},M=()=>{let y=n(F);f(t,e,y),a.current={key:R,pathname:g,search:c,hash:w,pagePath:C,url:G},h.current=!0;};if(d&&k.current){S.current=R,k.current.then(y=>{!T.current||S.current!==R||(D(y),M());}).catch(y=>{!T.current||S.current!==R||(console.error("[react-gtm-kit] Error while waiting for GTM readiness.",y),M());});return}M();},[n,t,e,D,f,o,i,l,d]);useEffect(()=>{var c;if(typeof window=="undefined")return;let g=i&&(c=window.location.hash)!=null?c:"";E(u,p,g);},[E,u,p,i]),useEffect(()=>{if(!i||typeof window=="undefined")return;let g=()=>{var c;E(u,p,(c=window.location.hash)!=null?c:"");};return window.addEventListener("hashchange",g),()=>{window.removeEventListener("hashchange",g);}},[E,u,p,i]);};var L="https://www.googletagmanager.com",Z=t=>typeof t=="string",I=t=>Z(t)?{id:t}:t,_=t=>Array.isArray(t)?t.map(I):[I(t)],tt=t=>t?Object.entries(t).reduce((e,[n,r])=>(e[n]=String(r),e),{}):{},et=t=>t.endsWith("/")?t.slice(0,-1):t,N=(t,e,n,r,i=DEFAULT_DATA_LAYER_NAME)=>{let l=et(e),o=new URLSearchParams({id:n}),f=tt(r);i!==DEFAULT_DATA_LAYER_NAME&&f.l===void 0&&(f.l=i);for(let[s,u]of Object.entries(f))s!=="id"&&o.set(s,u);return `${l}/${t==="gtm"?"gtm.js":"ns.html"}?${o.toString()}`},$=(t,e,n,r=DEFAULT_DATA_LAYER_NAME)=>N("gtm",t,e,n,r),H=(t,e,n,r=DEFAULT_DATA_LAYER_NAME)=>N("ns",t,e,n,r);var nt=!0,ot=({containers:t,host:e=L,defaultQueryParams:n,scriptAttributes:r,dataLayerName:i=DEFAULT_DATA_LAYER_NAME})=>{let l=_(t);if(!l.length)throw new Error("At least one GTM container is required to render script tags.");return jsx(Fragment,{children:l.map(o=>{if(!o.id)throw new Error("Container id is required to render GTM script tags.");let f={...n,...o.queryParams},d=$(e,o.id,f,i),{async:s,defer:u,nonce:m,...p}=r!=null?r:{},a={src:d,async:s!=null?s:nt};u!==void 0&&(a.defer=u),m&&(a.nonce=m);for(let[h,S]of Object.entries(p))h==="async"||h==="defer"||h==="nonce"||h==="src"||S!=null&&(a[h]=S);return jsx("script",{"data-gtm-container-id":o.id,...a},o.id)})})};var ct=t=>String(t),ut=t=>t.split(";").map(e=>e.trim()).filter(Boolean).reduce((e,n)=>{let[r,i]=n.split(":");if(!r||i===void 0)return e;let l=r.trim().replace(/-([a-z])/g,(f,d)=>d.toUpperCase()),o=i.trim();return !l||!o||(e[l]=o),e},{}),pt=({containers:t,host:e=L,defaultQueryParams:n,iframeAttributes:r,dataLayerName:i=DEFAULT_DATA_LAYER_NAME})=>{let l=_(t);if(!l.length)throw new Error("At least one GTM container is required to render noscript markup.");return jsx(Fragment,{children:l.map(o=>{if(!o.id)throw new Error("Container id is required to render GTM noscript markup.");let f={...n,...o.queryParams},d=H(e,o.id,f,i),s={...DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES,...r},u={src:d};for(let[m,p]of Object.entries(s))if(m!=="src"&&p!=null){if(m==="style"){typeof p=="string"?u.style=ut(p):typeof p=="object"&&(u.style=p);continue}u[m]=ct(p);}return jsx("noscript",{children:jsx("iframe",{...u})},o.id)})})};
|
|
7
|
+
|
|
8
|
+
export { ot as GtmHeadScript, pt as GtmNoScript, X as useTrackPageViews };
|
|
9
|
+
//# sourceMappingURL=out.js.map
|
|
10
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/route-listener.ts","../src/head-script.tsx","../src/internal/container-helpers.ts","../src/noscript.tsx"],"names":["useCallback","useEffect","useMemo","useRef","usePathname","useSearchParams","pushEvent","DEFAULT_EVENT_NAME","defaultBuildPayload","pagePath","url","title","payload","buildUrl","hash","_a","origin","sanitizeHash","useTrackPageViews","client","eventName","buildPayload","includeSearchParams","trackHash","trackOnMount","skipSamePath","pushEventFn","waitForReady","readyPromise","pathname","searchParams","search","previousRef","hasTrackedRef","pendingKeyRef","readinessRef","isMountedRef","logFailures","states","failed","state","details","handleRouteChange","nextPathname","searchValue","rawHash","normalizedHash","key","previous","pushPayload","error","listener","DEFAULT_DATA_LAYER_NAME","DEFAULT_GTM_HOST","isString","value","normalizeContainer","input","normalizeContainers","containers","toRecord","params","acc","normalizeHost","host","kind","containerId","queryParams","dataLayerName","normalizedHost","buildScriptUrl","buildNoscriptUrl","Fragment","jsx","DEFAULT_ASYNC","GtmHeadScript","defaultQueryParams","scriptAttributes","normalized","container","src","asyncAttr","defer","nonce","restAttributes","scriptProps","attribute","DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES","toStringValue","parseStyle","style","chunk","declaration","property","rawValue","_","char","GtmNoScript","iframeAttributes","attributes","iframeProps"],"mappings":"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","sourcesContent":["'use client';\n\nimport { useCallback, useEffect, useMemo, useRef } from 'react';\nimport { usePathname, useSearchParams } from 'next/navigation';\nimport type { GtmClient, PageViewPayload, ScriptLoadState } from '@jwiedeman/gtm-kit';\nimport { pushEvent } from '@jwiedeman/gtm-kit';\n\nconst DEFAULT_EVENT_NAME = 'page_view';\n\nexport interface RouteLocationSnapshot {\n pathname: string;\n search: string;\n hash: string;\n pagePath: string;\n url: string;\n}\n\nexport interface RouteChangeEventDetails extends RouteLocationSnapshot {\n title?: string;\n previous?: RouteLocationSnapshot;\n}\n\nexport type PageViewPayloadBuilder = (details: RouteChangeEventDetails) => PageViewPayload;\n\nexport interface UseTrackPageViewsOptions {\n client: Pick<GtmClient, 'push' | 'whenReady'>;\n eventName?: string;\n buildPayload?: PageViewPayloadBuilder;\n includeSearchParams?: boolean;\n trackHash?: boolean;\n trackOnMount?: boolean;\n skipSamePath?: boolean;\n pushEventFn?: typeof pushEvent;\n waitForReady?: boolean;\n readyPromise?: Promise<ScriptLoadState[]>;\n}\n\ninterface RouteSnapshot extends RouteLocationSnapshot {\n key: string;\n}\n\nconst defaultBuildPayload: PageViewPayloadBuilder = ({ pagePath, url, title }) => {\n const payload: PageViewPayload = {\n page_path: pagePath,\n page_location: url\n };\n\n if (title) {\n payload.page_title = title;\n }\n\n return payload;\n};\n\nconst buildUrl = (pagePath: string, hash: string): string => {\n const origin = typeof window !== 'undefined' && window.location?.origin ? window.location.origin : '';\n if (!origin) {\n return `${pagePath}${hash}`;\n }\n\n return `${origin}${pagePath}${hash}`;\n};\n\nconst sanitizeHash = (hash: string): string => (hash && hash.startsWith('#') ? hash : hash ? `#${hash}` : '');\n\nexport const useTrackPageViews = ({\n client,\n eventName = DEFAULT_EVENT_NAME,\n buildPayload = defaultBuildPayload,\n includeSearchParams = true,\n trackHash = false,\n trackOnMount = true,\n skipSamePath = true,\n pushEventFn = pushEvent,\n waitForReady = false,\n readyPromise\n}: UseTrackPageViewsOptions): void => {\n if (!client) {\n throw new Error('A GTM client is required to track page views.');\n }\n\n const pathname = usePathname();\n const searchParams = useSearchParams();\n\n const search = useMemo(() => {\n if (!includeSearchParams || !searchParams) {\n return '';\n }\n\n return searchParams.toString();\n }, [includeSearchParams, searchParams]);\n\n const previousRef = useRef<RouteSnapshot | null>(null);\n const hasTrackedRef = useRef(false);\n const pendingKeyRef = useRef<string | null>(null);\n const readinessRef = useRef<Promise<ScriptLoadState[]> | null>(\n waitForReady ? (readyPromise ?? client.whenReady()) : null\n );\n const isMountedRef = useRef(true);\n\n useEffect(() => {\n isMountedRef.current = true;\n return () => {\n isMountedRef.current = false;\n };\n }, []);\n\n useEffect(() => {\n readinessRef.current = waitForReady ? (readyPromise ?? client.whenReady()) : null;\n }, [client, readyPromise, waitForReady]);\n\n const logFailures = useCallback((states: ScriptLoadState[]) => {\n const failed = states.filter((state) => state.status === 'failed');\n if (!failed.length) {\n return;\n }\n\n const details = failed.map((state) => state.containerId).join(', ');\n // eslint-disable-next-line no-console\n console.error(`[react-gtm-kit] Failed to load GTM container script(s): ${details}`, failed);\n }, []);\n\n const handleRouteChange = useCallback(\n (nextPathname: string | null, searchValue: string, rawHash: string) => {\n if (!nextPathname) {\n return;\n }\n\n const normalizedHash = trackHash ? sanitizeHash(rawHash) : '';\n const pagePath = searchValue ? `${nextPathname}?${searchValue}` : nextPathname;\n const key = `${pagePath}${normalizedHash}`;\n const url = buildUrl(pagePath, normalizedHash);\n\n if (!trackOnMount && !hasTrackedRef.current) {\n previousRef.current = {\n key,\n pathname: nextPathname,\n search: searchValue,\n hash: normalizedHash,\n pagePath,\n url\n };\n hasTrackedRef.current = true;\n return;\n }\n\n if (skipSamePath && previousRef.current && previousRef.current.key === key) {\n return;\n }\n\n const title = typeof document !== 'undefined' ? document.title : undefined;\n const previous = previousRef.current\n ? {\n pathname: previousRef.current.pathname,\n search: previousRef.current.search,\n hash: previousRef.current.hash,\n pagePath: previousRef.current.pagePath,\n url: previousRef.current.url\n }\n : undefined;\n\n const details: RouteChangeEventDetails = {\n pathname: nextPathname,\n search: searchValue,\n hash: normalizedHash,\n pagePath,\n url,\n title,\n previous\n };\n\n const pushPayload = (): void => {\n const payload = buildPayload(details);\n pushEventFn(client, eventName, payload);\n\n previousRef.current = {\n key,\n pathname: nextPathname,\n search: searchValue,\n hash: normalizedHash,\n pagePath,\n url\n };\n hasTrackedRef.current = true;\n };\n\n if (waitForReady && readinessRef.current) {\n pendingKeyRef.current = key;\n readinessRef.current\n .then((states) => {\n if (!isMountedRef.current || pendingKeyRef.current !== key) {\n return;\n }\n\n logFailures(states);\n pushPayload();\n })\n .catch((error) => {\n if (!isMountedRef.current || pendingKeyRef.current !== key) {\n return;\n }\n // eslint-disable-next-line no-console\n console.error('[react-gtm-kit] Error while waiting for GTM readiness.', error);\n pushPayload();\n });\n\n return;\n }\n\n pushPayload();\n },\n [buildPayload, client, eventName, logFailures, pushEventFn, skipSamePath, trackHash, trackOnMount, waitForReady]\n );\n\n useEffect(() => {\n if (typeof window === 'undefined') {\n return;\n }\n\n const hash = trackHash ? (window.location.hash ?? '') : '';\n handleRouteChange(pathname, search, hash);\n }, [handleRouteChange, pathname, search, trackHash]);\n\n useEffect(() => {\n if (!trackHash || typeof window === 'undefined') {\n return;\n }\n\n const listener = (): void => {\n handleRouteChange(pathname, search, window.location.hash ?? '');\n };\n\n window.addEventListener('hashchange', listener);\n return () => {\n window.removeEventListener('hashchange', listener);\n };\n }, [handleRouteChange, pathname, search, trackHash]);\n};\n","import type React from 'react';\nimport { DEFAULT_DATA_LAYER_NAME } from '@jwiedeman/gtm-kit';\nimport type { ContainerConfigInput, ScriptAttributes } from '@jwiedeman/gtm-kit';\nimport { buildScriptUrl, DEFAULT_GTM_HOST, normalizeContainers } from './internal/container-helpers';\n\nexport interface GtmHeadScriptProps {\n containers: ContainerConfigInput | ContainerConfigInput[];\n host?: string;\n defaultQueryParams?: Record<string, string | number | boolean>;\n scriptAttributes?: ScriptAttributes;\n dataLayerName?: string;\n}\n\nconst DEFAULT_ASYNC = true;\n\nexport const GtmHeadScript = ({\n containers,\n host = DEFAULT_GTM_HOST,\n defaultQueryParams,\n scriptAttributes,\n dataLayerName = DEFAULT_DATA_LAYER_NAME\n}: GtmHeadScriptProps): React.ReactElement => {\n const normalized = normalizeContainers(containers);\n\n if (!normalized.length) {\n throw new Error('At least one GTM container is required to render script tags.');\n }\n\n return (\n <>\n {normalized.map((container) => {\n if (!container.id) {\n throw new Error('Container id is required to render GTM script tags.');\n }\n\n const params = {\n ...defaultQueryParams,\n ...container.queryParams\n };\n\n const src = buildScriptUrl(host, container.id, params, dataLayerName);\n const { async: asyncAttr, defer, nonce, ...restAttributes } = scriptAttributes ?? {};\n\n const scriptProps: React.ScriptHTMLAttributes<HTMLScriptElement> = {\n src,\n async: asyncAttr ?? DEFAULT_ASYNC\n };\n\n if (defer !== undefined) {\n scriptProps.defer = defer;\n }\n\n if (nonce) {\n scriptProps.nonce = nonce;\n }\n\n for (const [attribute, value] of Object.entries(restAttributes)) {\n if (attribute === 'async' || attribute === 'defer' || attribute === 'nonce' || attribute === 'src') {\n continue;\n }\n\n if (value === undefined || value === null) {\n continue;\n }\n\n (scriptProps as Record<string, unknown>)[attribute] = value;\n }\n\n return <script key={container.id} data-gtm-container-id={container.id} {...scriptProps} />;\n })}\n </>\n );\n};\n","import { DEFAULT_DATA_LAYER_NAME } from '@jwiedeman/gtm-kit';\nimport type { ContainerConfigInput, ContainerDescriptor } from '@jwiedeman/gtm-kit';\n\nexport const DEFAULT_GTM_HOST = 'https://www.googletagmanager.com';\n\nconst isString = (value: unknown): value is string => typeof value === 'string';\n\nexport const normalizeContainer = (input: ContainerConfigInput): ContainerDescriptor => {\n if (isString(input)) {\n return { id: input };\n }\n\n return input;\n};\n\nexport const normalizeContainers = (\n containers: ContainerConfigInput | ContainerConfigInput[]\n): ContainerDescriptor[] => {\n if (Array.isArray(containers)) {\n return containers.map(normalizeContainer);\n }\n\n return [normalizeContainer(containers)];\n};\n\nconst toRecord = (params?: Record<string, string | number | boolean>): Record<string, string> => {\n if (!params) {\n return {};\n }\n\n return Object.entries(params).reduce<Record<string, string>>((acc, [key, value]) => {\n acc[key] = String(value);\n return acc;\n }, {});\n};\n\nconst normalizeHost = (host: string): string => (host.endsWith('/') ? host.slice(0, -1) : host);\n\nconst buildUrl = (\n kind: 'gtm' | 'ns',\n host: string,\n containerId: string,\n queryParams?: Record<string, string | number | boolean>,\n dataLayerName: string = DEFAULT_DATA_LAYER_NAME\n): string => {\n const normalizedHost = normalizeHost(host);\n const searchParams = new URLSearchParams({ id: containerId });\n\n const params = toRecord(queryParams);\n if (dataLayerName !== DEFAULT_DATA_LAYER_NAME && params.l === undefined) {\n params.l = dataLayerName;\n }\n\n for (const [key, value] of Object.entries(params)) {\n if (key === 'id') {\n continue;\n }\n searchParams.set(key, value);\n }\n\n const suffix = kind === 'gtm' ? 'gtm.js' : 'ns.html';\n return `${normalizedHost}/${suffix}?${searchParams.toString()}`;\n};\n\nexport const buildScriptUrl = (\n host: string,\n containerId: string,\n queryParams?: Record<string, string | number | boolean>,\n dataLayerName: string = DEFAULT_DATA_LAYER_NAME\n): string => buildUrl('gtm', host, containerId, queryParams, dataLayerName);\n\nexport const buildNoscriptUrl = (\n host: string,\n containerId: string,\n queryParams?: Record<string, string | number | boolean>,\n dataLayerName: string = DEFAULT_DATA_LAYER_NAME\n): string => buildUrl('ns', host, containerId, queryParams, dataLayerName);\n","import type React from 'react';\nimport { DEFAULT_DATA_LAYER_NAME } from '@jwiedeman/gtm-kit';\nimport type { ContainerConfigInput } from '@jwiedeman/gtm-kit';\nimport { DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES } from '@jwiedeman/gtm-kit';\nimport { buildNoscriptUrl, DEFAULT_GTM_HOST, normalizeContainers } from './internal/container-helpers';\n\nexport interface GtmNoScriptProps {\n containers: ContainerConfigInput | ContainerConfigInput[];\n host?: string;\n defaultQueryParams?: Record<string, string | number | boolean>;\n iframeAttributes?: Record<string, string | number | boolean>;\n dataLayerName?: string;\n}\n\nconst toStringValue = (value: string | number | boolean): string => String(value);\n\nconst parseStyle = (style: string): React.CSSProperties => {\n return style\n .split(';')\n .map((chunk) => chunk.trim())\n .filter(Boolean)\n .reduce<React.CSSProperties>((acc, declaration) => {\n const [property, rawValue] = declaration.split(':');\n if (!property || rawValue === undefined) {\n return acc;\n }\n\n const key = property.trim().replace(/-([a-z])/g, (_, char: string) => char.toUpperCase());\n const value = rawValue.trim();\n if (!key || !value) {\n return acc;\n }\n\n (acc as Record<string, string>)[key] = value;\n return acc;\n }, {});\n};\n\nexport const GtmNoScript = ({\n containers,\n host = DEFAULT_GTM_HOST,\n defaultQueryParams,\n iframeAttributes,\n dataLayerName = DEFAULT_DATA_LAYER_NAME\n}: GtmNoScriptProps): React.ReactElement => {\n const normalized = normalizeContainers(containers);\n\n if (!normalized.length) {\n throw new Error('At least one GTM container is required to render noscript markup.');\n }\n\n return (\n <>\n {normalized.map((container) => {\n if (!container.id) {\n throw new Error('Container id is required to render GTM noscript markup.');\n }\n\n const params = {\n ...defaultQueryParams,\n ...container.queryParams\n };\n\n const src = buildNoscriptUrl(host, container.id, params, dataLayerName);\n const attributes = {\n ...DEFAULT_NOSCRIPT_IFRAME_ATTRIBUTES,\n ...iframeAttributes\n };\n\n const iframeProps: React.IframeHTMLAttributes<HTMLIFrameElement> = {\n src\n };\n\n for (const [attribute, value] of Object.entries(attributes)) {\n if (attribute === 'src') {\n continue;\n }\n\n if (value === undefined || value === null) {\n continue;\n }\n\n if (attribute === 'style') {\n if (typeof value === 'string') {\n iframeProps.style = parseStyle(value);\n } else if (typeof value === 'object') {\n iframeProps.style = value as React.CSSProperties;\n }\n continue;\n }\n\n (iframeProps as Record<string, unknown>)[attribute] = toStringValue(value as string | number | boolean);\n }\n\n return (\n <noscript key={container.id}>\n <iframe {...iframeProps} />\n </noscript>\n );\n })}\n </>\n );\n};\n"]}
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@jwiedeman/gtm-kit-next",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Next.js App Router helpers for GTM Kit - Google Tag Manager integration.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "https://github.com/jwiedeman/GTM-Kit.git",
|
|
8
|
+
"directory": "packages/next"
|
|
9
|
+
},
|
|
10
|
+
"author": "jwiedeman",
|
|
11
|
+
"keywords": [
|
|
12
|
+
"gtm",
|
|
13
|
+
"google-tag-manager",
|
|
14
|
+
"nextjs",
|
|
15
|
+
"next",
|
|
16
|
+
"app-router"
|
|
17
|
+
],
|
|
18
|
+
"license": "MIT",
|
|
19
|
+
"publishConfig": {
|
|
20
|
+
"access": "public"
|
|
21
|
+
},
|
|
22
|
+
"type": "module",
|
|
23
|
+
"main": "dist/index.cjs",
|
|
24
|
+
"module": "dist/index.js",
|
|
25
|
+
"types": "dist/index.d.ts",
|
|
26
|
+
"exports": {
|
|
27
|
+
".": {
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"import": "./dist/index.js",
|
|
30
|
+
"require": "./dist/index.cjs"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"dist"
|
|
35
|
+
],
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build": "tsup",
|
|
38
|
+
"clean": "rm -rf dist",
|
|
39
|
+
"lint": "eslint --max-warnings=0 \"src/**/*.{ts,tsx}\"",
|
|
40
|
+
"test": "jest --config ./jest.config.cjs --runInBand",
|
|
41
|
+
"typecheck": "tsc --noEmit"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@jwiedeman/gtm-kit": "^1.0.1"
|
|
45
|
+
},
|
|
46
|
+
"peerDependencies": {
|
|
47
|
+
"next": "^13.4.0 || ^14.0.0 || ^15.0.0",
|
|
48
|
+
"react": "^18.0.0 || ^19.0.0"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@testing-library/jest-dom": "^6.4.2",
|
|
52
|
+
"@testing-library/react": "^14.2.1",
|
|
53
|
+
"@types/react": "^18.3.0",
|
|
54
|
+
"@types/react-dom": "^18.3.0",
|
|
55
|
+
"react": "^18.3.1",
|
|
56
|
+
"react-dom": "^18.3.1",
|
|
57
|
+
"tslib": "^2.6.2"
|
|
58
|
+
}
|
|
59
|
+
}
|