Skip to content

Commit 2997ee2

Browse files
authored
Merge branch 'canary' into add/nft
2 parents 9823923 + da4d652 commit 2997ee2

File tree

6 files changed

+393
-1
lines changed

6 files changed

+393
-1
lines changed

docs/basic-features/eslint.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ Next.js provides an ESLint plugin, [`eslint-plugin-next`](https://www.npmjs.com/
9595
| ✔️ | [next/no-title-in-document-head](https://nextjs.org/docs/messages/no-title-in-document-head) | Disallow using <title> with Head from next/document |
9696
| ✔️ | [next/no-unwanted-polyfillio](https://nextjs.org/docs/messages/no-unwanted-polyfillio) | Prevent duplicate polyfills from Polyfill.io |
9797
| ✔️ | next/no-typos | Ensure no typos were made declaring [Next.js's data fetching function](https://nextjs.org/docs/basic-features/data-fetching) |
98+
| ✔️ | [next/next-script-for-ga](https://nextjs.org/docs/messages/next-script-for-ga) | Use the Script component to defer loading of the script until necessary. |
9899

99100
- ✔: Enabled in the recommended configuration
100101

errors/next-script-for-ga.md

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
# Next Script for Google Analytics
2+
3+
### Why This Error Occurred
4+
5+
An inline script was used for Google analytics which might impact your webpage's performance.
6+
7+
### Possible Ways to Fix It
8+
9+
#### Using gtag.js
10+
11+
If you are using the [gtag.js](https://developers.google.com/analytics/devguides/collection/gtagjs) script to add analytics, use the `next/script` component with the right loading strategy to defer loading of the script until necessary.
12+
13+
```jsx
14+
import Script from 'next/script'
15+
16+
const Home = () => {
17+
return (
18+
<div class="container">
19+
<!-- Global site tag (gtag.js) - Google Analytics -->
20+
<Script
21+
src="https://www.googletagmanager.com/gtag/js?id=GA_MEASUREMENT_ID"
22+
strategy="lazyOnload"
23+
></Script>
24+
<Script>
25+
{`
26+
window.dataLayer = window.dataLayer || [];
27+
function gtag(){window.dataLayer.push(arguments);}
28+
gtag('js', new Date());
29+
30+
gtag('config', 'GA_MEASUREMENT_ID');
31+
`}
32+
</Script>
33+
</div>
34+
)
35+
}
36+
37+
export default Home
38+
```
39+
40+
#### Using analytics.js
41+
42+
If you are using the [analytics.js](https://developers.google.com/analytics/devguides/collection/analyticsjs) script to add analytics:
43+
44+
```jsx
45+
import Script from 'next/script'
46+
47+
const Home = () => {
48+
return (
49+
<div class="container">
50+
<Script>
51+
{`
52+
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
53+
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
54+
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
55+
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
56+
57+
ga('create', 'UA-XXXXX-Y', 'auto');
58+
ga('send', 'pageview');
59+
`}
60+
</Script>
61+
</div>
62+
)
63+
}
64+
65+
export default Home
66+
```
67+
68+
If you are using the [alternative async variant](https://developers.google.com/analytics/devguides/collection/analyticsjs#alternative_async_tag):
69+
70+
```jsx
71+
import Script from 'next/script'
72+
73+
const Home = () => {
74+
return (
75+
<div class="container">
76+
<Script>
77+
{`
78+
window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
79+
ga('create', 'GOOGLE_ANALYTICS_ID', 'auto');
80+
ga('send', 'pageview');
81+
`}
82+
</Script>
83+
<Script
84+
src="https://www.google-analytics.com/analytics.js"
85+
strategy="lazyOnload"
86+
></Script>
87+
</div>
88+
)
89+
}
90+
91+
export default Home
92+
```
93+
94+
### Useful Links
95+
96+
- [Add analytics.js to Your Site](https://developers.google.com/analytics/devguides/collection/analyticsjs)
97+
- [Efficiently load third-party JavaScript](https://web.dev/efficiently-load-third-party-javascript/)
98+
- [next/script Documentation](https://nextjs.org/docs/basic-features/script)

packages/eslint-plugin-next/lib/index.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ module.exports = {
1616
'no-script-in-head': require('./rules/no-script-in-head'),
1717
'no-typos': require('./rules/no-typos'),
1818
'no-duplicate-head': require('./rules/no-duplicate-head'),
19+
'next-script-for-ga': require('./rules/next-script-for-ga'),
1920
},
2021
configs: {
2122
recommended: {
@@ -31,6 +32,7 @@ module.exports = {
3132
'@next/next/google-font-display': 1,
3233
'@next/next/google-font-preconnect': 1,
3334
'@next/next/link-passhref': 1,
35+
'@next/next/next-script-for-ga': 1,
3436
'@next/next/no-document-import-in-page': 2,
3537
'@next/next/no-head-import-in-document': 2,
3638
'@next/next/no-script-in-document': 2,
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
const NodeAttributes = require('../utils/node-attributes.js')
2+
3+
const SUPPORTED_SRCS = [
4+
'www.google-analytics.com/analytics.js',
5+
'www.googletagmanager.com/gtag/js',
6+
]
7+
const SUPPORTED_HTML_CONTENT_URLS = [
8+
'www.google-analytics.com/analytics.js',
9+
'www.googletagmanager.com/gtm.js',
10+
]
11+
const ERROR_MSG =
12+
'Use the `next/script` component for loading third party scripts. See: https://nextjs.org/docs/messages/next-script-for-ga.'
13+
14+
// Check if one of the items in the list is a substring of the passed string
15+
const containsStr = (str, strList) => {
16+
return strList.some((s) => str.includes(s))
17+
}
18+
19+
module.exports = {
20+
meta: {
21+
docs: {
22+
description:
23+
'Prefer next script component when using the inline script for Google Analytics',
24+
recommended: true,
25+
},
26+
},
27+
create: function (context) {
28+
return {
29+
JSXOpeningElement(node) {
30+
if (node.name.name !== 'script') {
31+
return
32+
}
33+
if (node.attributes.length === 0) {
34+
return
35+
}
36+
const attributes = new NodeAttributes(node)
37+
38+
// Check if the Alternative async tag is being used to add GA.
39+
// https://developers.google.com/analytics/devguides/collection/analyticsjs#alternative_async_tag
40+
// https://developers.google.com/analytics/devguides/collection/gtagjs
41+
if (
42+
typeof attributes.value('src') === 'string' &&
43+
containsStr(attributes.value('src'), SUPPORTED_SRCS)
44+
) {
45+
return context.report({
46+
node,
47+
message: ERROR_MSG,
48+
})
49+
}
50+
51+
// Check if inline script is being used to add GA.
52+
// https://developers.google.com/analytics/devguides/collection/analyticsjs#the_google_analytics_tag
53+
// https://developers.google.com/tag-manager/quickstart
54+
if (
55+
attributes.has('dangerouslySetInnerHTML') &&
56+
attributes.value('dangerouslySetInnerHTML')[0]
57+
) {
58+
const htmlContent =
59+
attributes.value('dangerouslySetInnerHTML')[0].value.quasis &&
60+
attributes.value('dangerouslySetInnerHTML')[0].value.quasis[0].value
61+
.raw
62+
if (
63+
htmlContent &&
64+
containsStr(htmlContent, SUPPORTED_HTML_CONTENT_URLS)
65+
) {
66+
context.report({
67+
node,
68+
message: ERROR_MSG,
69+
})
70+
}
71+
}
72+
},
73+
}
74+
},
75+
}
76+
77+
module.exports.schema = []

packages/eslint-plugin-next/lib/utils/node-attributes.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ class NodeAttributes {
2626
this.attributes[attribute.name.name].value = attribute.value.value
2727
} else if (attribute.value.expression) {
2828
this.attributes[attribute.name.name].value =
29-
attribute.value.expression.value
29+
typeof attribute.value.expression.value !== 'undefined'
30+
? attribute.value.expression.value
31+
: attribute.value.expression.properties
3032
}
3133
}
3234
})

0 commit comments

Comments
 (0)