Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 38 additions & 7 deletions errors/next-script-for-ga.mdx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
---
title: Using Google Analytics with Next.js (through `next/script`)
title: Using Google Analytics with Next.js (through `@next/third-parties/google`)
---

> Prefer `next/script` component when using the inline script for Google Analytics.
> Prefer `@next/third-parties/google` when using the inline script for Google Analytics and Tag Manager.

## Why This Error Occurred

Expand Down Expand Up @@ -63,11 +63,42 @@ export default function Page() {
}
```

> **Good to know:**
>
> - If you are using the Pages Router, please refer to the [`pages/` documentation](/docs/pages/guides/third-party-libraries).
> - `@next/third-parties` also supports [Google Tag Manager](/docs/app/guides/third-party-libraries#google-tag-manager) and other third parties.
> - Using `@next/third-parties` is not required. You can also use the `next/script` component directly. Refer to the [`next/script` documentation](/docs/app/guides/scripts) to learn more.
### Use `@next/third-parties` to add Google Tag Manager

The `GoogleTagManager` component can be used to add [Google Tag Manager](https://developers.google.com/tag-manager/quickstart) to your page.

```tsx filename="app/layout.tsx" switcher
import { GoogleTagManager } from '@next/third-parties/google'

export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<GoogleTagManager gtmId="GTM-XYZ" />
<body>{children}</body>
</html>
)
}
```

To load Google Tag Manager for a single route, include the component in your page file:

```jsx filename="app/page.js"
import { GoogleTagManager } from '@next/third-parties/google'

export default function Page() {
return <GoogleTagManager gtmId="GTM-XYZ" />
}
```

## Good to know

- If you are using the Pages Router, please refer to the [`pages/` documentation](/docs/pages/guides/third-party-libraries).
- `@next/third-parties` also supports [other third parties](/docs/app/guides/third-party-libraries#google-tag-manager).
- Using `@next/third-parties` is not required. You can also use the `next/script` component directly. Refer to the [`next/script` documentation](/docs/app/guides/scripts) to learn more.

## Useful Links

Expand Down
63 changes: 33 additions & 30 deletions packages/eslint-plugin-next/src/rules/next-script-for-ga.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,17 @@
import { defineRule } from '../utils/define-rule'
import NodeAttributes from '../utils/node-attributes'

const SUPPORTED_SRCS = [
'www.google-analytics.com/analytics.js',
'www.googletagmanager.com/gtag/js',
]
const SUPPORTED_HTML_CONTENT_URLS = [
'www.google-analytics.com/analytics.js',
'www.googletagmanager.com/gtm.js',
]
const GOOGLE_ANALYTICS_URL = 'www.google-analytics.com/analytics.js'
const GOOGLE_TAG_MANAGER_URL = 'www.googletagmanager.com/gtag/js'

const GOOGLE_ANALYTICS_SRC = GOOGLE_ANALYTICS_URL
const GOOGLE_TAG_MANAGER_SRC = 'www.googletagmanager.com/gtm.js'

const description =
'Prefer `next/script` component when using the inline script for Google Analytics.'
'Prefer `@next/third-parties/google` when using the inline script for Google Analytics and Tag Manager.'
const url = 'https://nextjs.org/docs/messages/next-script-for-ga'
const ERROR_MSG = `${description} See: ${url}`

// Check if one of the items in the list is a substring of the passed string
const containsStr = (str, strList) => {
return strList.some((s) => str.includes(s))
}
const ERROR_MSG_GOOGLE_ANALYTICS = `Prefer \`GoogleAnalytics\` component from \`@next/third-parties/google\` when using the inline script for Google Analytics. See: ${url}`
const ERROR_MSG_GOOGLE_TAG_MANAGER = `Prefer \`GoogleTagManager\` component from \`@next/third-parties/google\` when using the inline script for Google Tag Manager. See: ${url}`

export default defineRule({
meta: {
Expand All @@ -40,37 +34,46 @@ export default defineRule({
}
const attributes = new NodeAttributes(node)

const src = attributes.value('src')
// Check if the Alternative async tag is being used to add GA.
// https://developers.google.com/analytics/devguides/collection/analyticsjs#alternative_async_tag
// https://developers.google.com/analytics/devguides/collection/gtagjs
if (
typeof attributes.value('src') === 'string' &&
containsStr(attributes.value('src'), SUPPORTED_SRCS)
if (typeof src === 'string' && src.includes(GOOGLE_ANALYTICS_URL)) {
return context.report({
node,
message: ERROR_MSG_GOOGLE_ANALYTICS,
})
} else if (
typeof src === 'string' &&
src.includes(GOOGLE_TAG_MANAGER_URL)
) {
return context.report({
node,
message: ERROR_MSG,
message: ERROR_MSG_GOOGLE_TAG_MANAGER,
})
}

const dangerouslySetInnerHTML = attributes.value(
'dangerouslySetInnerHTML'
)
// Check if inline script is being used to add GA.
// https://developers.google.com/analytics/devguides/collection/analyticsjs#the_google_analytics_tag
// https://developers.google.com/tag-manager/quickstart
if (
attributes.value('dangerouslySetInnerHTML') &&
attributes.value('dangerouslySetInnerHTML').length > 0
) {
const htmlContent =
attributes.value('dangerouslySetInnerHTML')[0].value.quasis &&
attributes.value('dangerouslySetInnerHTML')[0].value.quasis[0].value
.raw
if (
if (dangerouslySetInnerHTML && dangerouslySetInnerHTML.length > 0) {
const quasis = dangerouslySetInnerHTML[0].value.quasis
const htmlContent = quasis?.[0]?.value?.raw
if (htmlContent && htmlContent.includes(GOOGLE_ANALYTICS_SRC)) {
context.report({
node,
message: ERROR_MSG_GOOGLE_ANALYTICS,
})
} else if (
htmlContent &&
containsStr(htmlContent, SUPPORTED_HTML_CONTENT_URLS)
htmlContent.includes(GOOGLE_TAG_MANAGER_SRC)
) {
context.report({
node,
message: ERROR_MSG,
message: ERROR_MSG_GOOGLE_TAG_MANAGER,
})
}
}
Expand Down
42 changes: 7 additions & 35 deletions test/unit/eslint-plugin-next/next-script-for-ga.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ import { rules } from '@next/eslint-plugin-next'

const NextESLintRule = rules['next-script-for-ga']

const ERROR_MSG =
'Prefer `next/script` component when using the inline script for Google Analytics. See: https://nextjs.org/docs/messages/next-script-for-ga'
const url = 'https://nextjs.org/docs/messages/next-script-for-ga'
const ERROR_MSG_GOOGLE_ANALYTICS = `Prefer \`GoogleAnalytics\` component from \`@next/third-parties/google\` when using the inline script for Google Analytics. See: ${url}`
const ERROR_MSG_GOOGLE_TAG_MANAGER = `Prefer \`GoogleTagManager\` component from \`@next/third-parties/google\` when using the inline script for Google Tag Manager. See: ${url}`

const tests = {
valid: [
Expand Down Expand Up @@ -108,36 +109,7 @@ const tests = {
}`,
errors: [
{
message: ERROR_MSG,
type: 'JSXOpeningElement',
},
],
},
{
code: `
export class Blah extends Head {
render() {
return (
<div>
<h1>Hello title</h1> qqq
{/* Google Tag Manager - Global base code */}
<script
dangerouslySetInnerHTML={{
__html: \`
(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start':
new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0],
j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src=
'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f);
})(window,document,'script','dataLayer', '\${GTM_ID}');
\`,
}}/>
</div>
);
}
}`,
errors: [
{
message: ERROR_MSG,
message: ERROR_MSG_GOOGLE_TAG_MANAGER,
type: 'JSXOpeningElement',
},
],
Expand Down Expand Up @@ -166,7 +138,7 @@ const tests = {
}`,
errors: [
{
message: ERROR_MSG,
message: ERROR_MSG_GOOGLE_ANALYTICS,
type: 'JSXOpeningElement',
},
],
Expand All @@ -192,7 +164,7 @@ const tests = {
}`,
errors: [
{
message: ERROR_MSG,
message: ERROR_MSG_GOOGLE_ANALYTICS,
type: 'JSXOpeningElement',
},
],
Expand Down Expand Up @@ -222,7 +194,7 @@ const tests = {
}`,
errors: [
{
message: ERROR_MSG,
message: ERROR_MSG_GOOGLE_ANALYTICS,
type: 'JSXOpeningElement',
},
],
Expand Down
Loading