Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add a lint rule for using next script component when using inline scr…
…ipt for Google Analytics.
- Loading branch information
Showing
4 changed files
with
262 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
# Next Script for Google Analytics | ||
|
||
### Why This Error Occurred | ||
|
||
An inline script was used for Google analytics which might impact your webpage's performance. | ||
|
||
### Possible Ways to Fix It | ||
|
||
#### Script component (experimental) | ||
|
||
Use the Script component with the right loading strategy to defer loading of the script until necessary. | ||
|
||
```jsx | ||
import Script from 'next/experimental-script' | ||
|
||
const Home = () => { | ||
return ( | ||
<div class="container"> | ||
<Script> | ||
{` | ||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ | ||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), | ||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) | ||
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); | ||
ga('create', 'UA-XXXXX-Y', 'auto'); | ||
ga('send', 'pageview'); | ||
}) | ||
`} | ||
</Script> | ||
</div> | ||
) | ||
} | ||
|
||
export default Home | ||
``` | ||
|
||
If you are using the [alternative async variant](https://developers.google.com/analytics/devguides/collection/analyticsjs#alternative_async_tag): | ||
|
||
```jsx | ||
import Script from 'next/experimental-script' | ||
|
||
const Home = () => { | ||
return ( | ||
<div class="container"> | ||
<Script> | ||
{` | ||
window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date; | ||
ga('create', 'UA-XXXXX-Y', 'auto'); | ||
ga('send', 'pageview'); | ||
}) | ||
`} | ||
</Script> | ||
<Script | ||
src="https://www.google-analytics.com/analytics.js" | ||
strategy="lazyOnload" | ||
></Script> | ||
</div> | ||
) | ||
} | ||
|
||
export default Home | ||
``` | ||
|
||
Note: This is still an experimental feature and needs to be enabled via the `experimental.scriptLoader` flag in `next.config.js`. | ||
|
||
### Useful Links | ||
|
||
- [Add analytics.js to Your Site](https://developers.google.com/analytics/devguides/collection/analyticsjs) | ||
- [Efficiently load third-party JavaScript](https://web.dev/efficiently-load-third-party-javascript/) |
60 changes: 60 additions & 0 deletions
60
packages/eslint-plugin-next/lib/rules/next-script-for-ga.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
const NodeAttributes = require('../utils/nodeAttributes.js') | ||
|
||
const GA_URL = 'www.google-analytics.com/analytics.js' | ||
const ERROR_MSG = | ||
'Use the Script component for loading third party scripts. See: https://nextjs.org/docs/messages/next-script-for-ga.' | ||
|
||
module.exports = { | ||
meta: { | ||
docs: { | ||
description: | ||
'Prefer next script component when using the inline script for Google Analytics', | ||
recommended: true, | ||
}, | ||
}, | ||
create: function (context) { | ||
return { | ||
JSXOpeningElement(node) { | ||
if (node.name.name !== 'script') { | ||
return | ||
} | ||
if (node.attributes.length === 0) { | ||
return | ||
} | ||
const attributes = new NodeAttributes(node) | ||
|
||
// Check if the Alternative async tag is being used to add GA. https://developers.google.com/analytics/devguides/collection/analyticsjs#alternative_async_tag | ||
if ( | ||
typeof attributes.value('src') === 'string' && | ||
attributes.value('src').includes(GA_URL) | ||
) { | ||
return context.report({ | ||
node, | ||
message: ERROR_MSG, | ||
}) | ||
} | ||
|
||
// Check if inline script is being used to add GA. https://developers.google.com/analytics/devguides/collection/analyticsjs#the_google_analytics_tag | ||
if ( | ||
attributes.has('dangerouslySetInnerHTML') && | ||
attributes.value('dangerouslySetInnerHTML')[0] | ||
) { | ||
const htmlContent = attributes.value('dangerouslySetInnerHTML')[0] | ||
.value.quasis[0].value.raw | ||
|
||
if ( | ||
htmlContent && | ||
htmlContent.includes('www.google-analytics.com/analytics.js') | ||
) { | ||
context.report({ | ||
node, | ||
message: ERROR_MSG, | ||
}) | ||
} | ||
} | ||
}, | ||
} | ||
}, | ||
} | ||
|
||
module.exports.schema = [] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
129 changes: 129 additions & 0 deletions
129
test/eslint-plugin-next/next-script-for-ga.unit.test.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
const rule = require('@next/eslint-plugin-next/lib/rules/next-script-for-ga') | ||
|
||
const RuleTester = require('eslint').RuleTester | ||
|
||
const ERROR_MSG = | ||
'Use the Script component for loading third party scripts. See: https://nextjs.org/docs/messages/next-script-for-ga.' | ||
|
||
RuleTester.setDefaultConfig({ | ||
parserOptions: { | ||
ecmaVersion: 2018, | ||
sourceType: 'module', | ||
ecmaFeatures: { | ||
modules: true, | ||
jsx: true, | ||
}, | ||
}, | ||
}) | ||
|
||
var ruleTester = new RuleTester() | ||
ruleTester.run('sync-scripts', rule, { | ||
valid: [ | ||
`import Script from 'next/experimental-script' | ||
export class Blah extends Head { | ||
render() { | ||
return ( | ||
<div> | ||
<h1>Hello title</h1> | ||
<Script> | ||
{\`(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ | ||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), | ||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) | ||
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); | ||
ga('create', 'UA-XXXXX-Y', 'auto'); | ||
ga('send', 'pageview'); | ||
})\`} | ||
</Script> | ||
</div> | ||
); | ||
} | ||
}`, | ||
`import Script from 'next/experimental-script' | ||
export class Blah extends Head { | ||
render() { | ||
return ( | ||
<div> | ||
<h1>Hello title</h1> | ||
<Script> | ||
{\`window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date; | ||
ga('create', 'UA-XXXXX-Y', 'auto'); | ||
ga('send', 'pageview'); | ||
})\`} | ||
</Script> | ||
</div> | ||
); | ||
} | ||
}`, | ||
`export class Blah extends Head { | ||
render() { | ||
return ( | ||
<div> | ||
<h1>Hello title</h1> | ||
<script dangerouslySetInnerHTML={{}} /> | ||
</div> | ||
); | ||
} | ||
}`, | ||
], | ||
|
||
invalid: [ | ||
{ | ||
code: ` | ||
export class Blah extends Head { | ||
render() { | ||
return ( | ||
<div> | ||
<h1>Hello title</h1> | ||
<script dangerouslySetInnerHTML={{ | ||
__html: \` | ||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ | ||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), | ||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) | ||
})(window,document,'script','https://www.google-analytics.com/analytics.js','ga'); | ||
ga('create', 'UA-XXXXX-Y', 'auto'); | ||
ga('send', 'pageview'); | ||
\`, | ||
}}/> | ||
</div> | ||
); | ||
} | ||
}`, | ||
errors: [ | ||
{ | ||
message: ERROR_MSG, | ||
type: 'JSXOpeningElement', | ||
}, | ||
], | ||
}, | ||
{ | ||
code: ` | ||
export class Blah extends Head { | ||
render() { | ||
return ( | ||
<div> | ||
<h1>Hello title</h1> | ||
<script dangerouslySetInnerHTML={{ | ||
__html: \` | ||
window.ga=window.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date; | ||
ga('create', 'UA-XXXXX-Y', 'auto'); | ||
ga('send', 'pageview'); | ||
\`, | ||
}}/> | ||
<script async src='https://www.google-analytics.com/analytics.js'></script> | ||
</div> | ||
); | ||
} | ||
}`, | ||
errors: [ | ||
{ | ||
message: ERROR_MSG, | ||
type: 'JSXOpeningElement', | ||
}, | ||
], | ||
}, | ||
], | ||
}) |