Skip to content

Commit 5e474a3

Browse files
Adding useGoogleTagManager hook to @next/third-parties (#56106)
This PR adds the `useGoogleTagManage` hook to `@next/third-parties` repo.
1 parent df1d4a1 commit 5e474a3

File tree

9 files changed

+165
-3
lines changed

9 files changed

+165
-3
lines changed

packages/third-parties/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
{
22
"name": "@next/third-parties",
33
"version": "13.5.6-canary.3",
4-
"private": true,
54
"repository": {
65
"url": "vercel/next.js",
76
"directory": "packages/third-parties"
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
'use client'
2+
// TODO: Evaluate import 'client only'
3+
import React from 'react'
4+
import Script from 'next/script'
5+
6+
declare global {
7+
interface Window {
8+
dataLayer?: Object[]
9+
[key: string]: any
10+
}
11+
}
12+
13+
type GTMParams = {
14+
gtmId: string
15+
dataLayer: string[]
16+
dataLayerName: string
17+
auth: string
18+
preview: string
19+
}
20+
21+
let currDataLayerName: string | undefined = undefined
22+
23+
export function GoogleTagManager(props: GTMParams) {
24+
const { gtmId, dataLayerName = 'dataLayer', auth, preview, dataLayer } = props
25+
26+
if (currDataLayerName === undefined) {
27+
currDataLayerName = dataLayerName
28+
}
29+
30+
const gtmLayer = dataLayerName !== 'dataLayer' ? `$l=${dataLayerName}` : ''
31+
const gtmAuth = auth ? `&gtm_auth=${auth}` : ''
32+
const gtmPreview = preview ? `&gtm_preview=${preview}&gtm_cookies_win=x` : ''
33+
34+
return (
35+
<>
36+
<Script
37+
id="_next-gtm-init"
38+
dangerouslySetInnerHTML={{
39+
__html: `
40+
(function(w,l){
41+
w[l]=w[l]||[];
42+
w[l].push({'gtm.start': new Date().getTime(),event:'gtm.js'});
43+
${dataLayer ? `w[l].push(${JSON.stringify(dataLayer)})` : ''}
44+
})(window,'${dataLayerName}');`,
45+
}}
46+
/>
47+
<Script
48+
id="_next-gtm"
49+
src={`https://www.googletagmanager.com/gtm.js?id=${gtmId}${gtmLayer}${gtmAuth}${gtmPreview}`}
50+
/>
51+
</>
52+
)
53+
}
54+
55+
export const sendGTMEvent = (data: Object) => {
56+
if (currDataLayerName === undefined) {
57+
console.warn(`@next/third-parties: GTM has not been initialized`)
58+
return
59+
}
60+
61+
if (window[currDataLayerName]) {
62+
window[currDataLayerName].push(data)
63+
} else {
64+
console.warn(
65+
`@next/third-parties: GTM dataLayer ${currDataLayerName} does not exist`
66+
)
67+
}
68+
}
Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
1-
export { default as GoogleMapsEmbed } from './GoogleMapsEmbed'
2-
export { default as YouTubeEmbed } from './YouTubeEmbed'
1+
export { default as GoogleMapsEmbed } from './google-maps-embed'
2+
export { default as YouTubeEmbed } from './youtube-embed'
3+
export { GoogleTagManager, sendGTMEvent } from './gtm'
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
'use client'
2+
3+
import React from 'react'
4+
import { GoogleTagManager, sendGTMEvent } from '@next/third-parties/google'
5+
6+
const Page = () => {
7+
const onClick = () => {
8+
sendGTMEvent({ event: 'buttonClicked', value: 'xyz' })
9+
}
10+
11+
return (
12+
<div class="container">
13+
<GoogleTagManager gtmId="GTM-XYZ" />
14+
<h1>GTM</h1>
15+
<button id="gtm-send" onClick={onClick}>
16+
Click
17+
</button>
18+
<GoogleTagManager gtmId="GTM-XYZ" />
19+
</div>
20+
)
21+
}
22+
23+
export default Page

test/e2e/app-dir/third-parties/basic.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { createNextDescribe } from 'e2e-utils'
2+
import { waitFor } from 'next-test-utils'
23

34
createNextDescribe(
45
'@next/third-parties basic usage',
@@ -29,5 +30,29 @@ createNextDescribe(
2930
expect(baseContainer.length).toBe(1)
3031
expect(mapContainer.length).toBe(1)
3132
})
33+
34+
it('renders GTM', async () => {
35+
const browser = await next.browser('/gtm')
36+
37+
await browser.waitForElementByCss('#_next-gtm')
38+
await waitFor(1000)
39+
40+
const gtmInlineScript = await browser.elementsByCss('#_next-gtm-init')
41+
expect(gtmInlineScript.length).toBe(1)
42+
43+
const gtmScript = await browser.elementsByCss(
44+
'[src^="https://www.googletagmanager.com/gtm.js?id=GTM-XYZ"]'
45+
)
46+
47+
expect(gtmScript.length).toBe(1)
48+
49+
const dataLayer = await browser.eval('window.dataLayer')
50+
expect(dataLayer.length).toBe(1)
51+
52+
await browser.elementByCss('#gtm-send').click()
53+
54+
const dataLayer2 = await browser.eval('window.dataLayer')
55+
expect(dataLayer2.length).toBe(2)
56+
})
3257
}
3358
)

test/e2e/third-parties/index.test.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { createNextDescribe } from 'e2e-utils'
2+
import { waitFor } from 'next-test-utils'
23

34
createNextDescribe(
45
'@next/third-parties basic usage',
@@ -28,5 +29,29 @@ createNextDescribe(
2829
expect(baseContainer.length).toBe(1)
2930
expect(mapContainer.length).toBe(1)
3031
})
32+
33+
it('renders GTM', async () => {
34+
const browser = await next.browser('/gtm')
35+
36+
await browser.waitForElementByCss('#_next-gtm')
37+
await waitFor(1000)
38+
39+
const gtmInlineScript = await browser.elementsByCss('#_next-gtm-init')
40+
expect(gtmInlineScript.length).toBe(1)
41+
42+
const gtmScript = await browser.elementsByCss(
43+
'[src^="https://www.googletagmanager.com/gtm.js?id=GTM-XYZ"]'
44+
)
45+
46+
expect(gtmScript.length).toBe(1)
47+
48+
const dataLayer = await browser.eval('window.dataLayer')
49+
expect(dataLayer.length).toBe(1)
50+
51+
await browser.elementByCss('#gtm-send').click()
52+
53+
const dataLayer2 = await browser.eval('window.dataLayer')
54+
expect(dataLayer2.length).toBe(2)
55+
})
3156
}
3257
)

test/e2e/third-parties/pages/gtm.js

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import React from 'react'
2+
import { GoogleTagManager, sendGTMEvent } from '@next/third-parties/google'
3+
4+
const Page = () => {
5+
const onClick = () => {
6+
sendGTMEvent({ event: 'buttonClicked', value: 'xyz' })
7+
}
8+
9+
return (
10+
<div class="container">
11+
<GoogleTagManager gtmId="GTM-XYZ" />
12+
<h1>GTM</h1>
13+
<button id="gtm-send" onClick={onClick}>
14+
Click
15+
</button>
16+
<GoogleTagManager gtmId="GTM-XYZ" />
17+
</div>
18+
)
19+
}
20+
21+
export default Page

0 commit comments

Comments
 (0)