├── .eslintrc.json ├── .github ├── dependabot.yml └── workflows │ ├── dependabot-auto-merge.yml │ └── main.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── jest.config.js ├── package-lock.json ├── package.json ├── src ├── components │ ├── GoogleAnalytics.test.tsx │ ├── GoogleAnalytics.tsx │ └── index.ts ├── hooks │ ├── index.ts │ ├── usePageViews.ts │ └── usePagesViews.ts ├── index.ts └── interactions │ ├── consent.test.ts │ ├── consent.ts │ ├── event.test.ts │ ├── event.ts │ ├── index.ts │ ├── pageView.test.ts │ └── pageView.ts └── tsconfig.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true 5 | }, 6 | "settings": { 7 | "react": { 8 | "version": "detect" 9 | } 10 | }, 11 | "extends": [ 12 | "eslint:recommended", 13 | "plugin:react/recommended", 14 | "plugin:@typescript-eslint/recommended", 15 | "prettier" 16 | ], 17 | "parser": "@typescript-eslint/parser", 18 | "parserOptions": { 19 | "ecmaFeatures": { 20 | "jsx": true 21 | }, 22 | "ecmaVersion": 12, 23 | "sourceType": "module" 24 | }, 25 | "plugins": ["react", "@typescript-eslint"], 26 | "rules": {} 27 | } 28 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "npm" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | commit-message: 8 | prefix: fix 9 | prefix-development: chore 10 | include: scope 11 | -------------------------------------------------------------------------------- /.github/workflows/dependabot-auto-merge.yml: -------------------------------------------------------------------------------- 1 | name: Dependabot auto-merge 2 | 3 | on: pull_request_target 4 | 5 | permissions: 6 | pull-requests: write 7 | contents: write 8 | 9 | jobs: 10 | auto-merge-dependabot-pr: 11 | runs-on: ubuntu-latest 12 | if: ${{ github.event.pull_request.user.login == 'dependabot[bot]' }} 13 | steps: 14 | - name: Dependabot metadata 15 | id: dependabot-metadata 16 | uses: dependabot/fetch-metadata@v1.3.1 17 | - name: Enable auto-merge for patch and minor updates 18 | if: ${{steps.dependabot-metadata.outputs.update-type == 'version-update:semver-patch' || steps.dependabot-metadata.outputs.update-type == 'version-update:semver-minor'}} 19 | run: gh pr merge --auto --squash "$PR_URL" 20 | env: 21 | PR_URL: ${{github.event.pull_request.html_url}} 22 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 23 | - name: Enable auto-merge for major updates of development dependencies 24 | if: ${{steps.dependabot-metadata.outputs.update-type == 'version-update:semver-major' && steps.dependabot-metadata.outputs.dependency-type == 'direct:development'}} 25 | run: gh pr merge --auto --squash "$PR_URL" 26 | env: 27 | PR_URL: ${{github.event.pull_request.html_url}} 28 | GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} 29 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | jobs: 8 | build-and-test: 9 | name: Build, lint, and test 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | - uses: actions/setup-node@v4 14 | with: 15 | node-version: 20 16 | cache: "npm" 17 | - name: Install Dependencies 18 | run: npm ci 19 | - name: Lint 20 | run: npm run lint 21 | - name: Test 22 | run: npm run test -- --ci --coverage 23 | - name: Codecov 24 | run: npx codecov 25 | - name: Build 26 | run: npm run build 27 | - name: Upload build 28 | if: github.ref == 'refs/heads/main' 29 | uses: actions/upload-artifact@v4 30 | with: 31 | name: dist 32 | path: dist 33 | release: 34 | runs-on: ubuntu-latest 35 | needs: build-and-test 36 | if: github.ref == 'refs/heads/main' 37 | steps: 38 | - uses: actions/checkout@v4 39 | - uses: actions/setup-node@v4 40 | with: 41 | node-version: 20 42 | cache: "npm" 43 | - name: Install Dependencies 44 | run: npm ci 45 | - name: Download build 46 | uses: actions/download-artifact@v4 47 | with: 48 | name: dist 49 | - name: Release 50 | env: 51 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 52 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 53 | run: npx semantic-release 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | coverage 4 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | . 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Mauricio Robayo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > [!NOTE] 2 | > You might not need this package. Please check [Third Party Libraries](https://nextjs.org/docs/app/building-your-application/optimizing/third-party-libraries) first. 3 | 4 | # Nextjs Google Analytics 5 | 6 | [![npm version](https://badge.fury.io/js/nextjs-google-analytics.svg)](https://badge.fury.io/js/nextjs-google-analytics) 7 | [![codecov](https://codecov.io/gh/MauricioRobayo/nextjs-google-analytics/branch/main/graph/badge.svg?token=ywhhMAVgON)](https://codecov.io/gh/MauricioRobayo/nextjs-google-analytics) 8 | [![CodeFactor](https://www.codefactor.io/repository/github/mauriciorobayo/nextjs-google-analytics/badge)](https://www.codefactor.io/repository/github/mauriciorobayo/nextjs-google-analytics) 9 | 10 | **Google Analytics for Next.js** 11 | 12 | This package optimizes script loading using [Next.js `Script` tag](https://nextjs.org/docs/basic-features/script), which means that it will **only work on apps using Next.js >= 11.0.0**. 13 | 14 | ## Installation 15 | 16 | ``` 17 | npm install --save nextjs-google-analytics 18 | ``` 19 | 20 | ## TL;DR 21 | 22 | Add the `GoogleAnalytics` component with the `trackPageViews` prop set to `true` to your [custom App](https://nextjs.org/docs/advanced-features/custom-app) file: 23 | 24 | ```js 25 | // pages/_app.js 26 | import { GoogleAnalytics } from "nextjs-google-analytics"; 27 | 28 | const App = ({ Component, pageProps }) => { 29 | return ( 30 | <> 31 | 32 | 33 | 34 | ); 35 | }; 36 | 37 | export default App; 38 | ``` 39 | 40 | You can pass your _Google Analytics measurement id_ by setting it on the `NEXT_PUBLIC_GA_MEASUREMENT_ID` environment variable or using the `gaMeasurementId` prop on the `GoogleAnalytics` component. **The environment variable will override the prop if both are set**. 41 | 42 | ## Usage 43 | 44 | Your _Google Analytics measurement id_ is read from `NEXT_PUBLIC_GA_MEASUREMENT_ID` environment variable, so make sure it is set in your production environment: 45 | 46 | - [Vercel](https://vercel.com/docs/environment-variables) 47 | - [Netlify](https://www.netlify.com/blog/2020/12/10/environment-variables-in-next.js-and-netlify/) 48 | 49 | If the variable is not set or is empty, nothing will be loaded, making it safe to work in development. 50 | 51 | To load it and test it on development, add: 52 | 53 | ``` 54 | NEXT_PUBLIC_GA_MEASUREMENT_ID="G-XXXXXXXXXX" 55 | ``` 56 | 57 | to your `.env.local` file. 58 | 59 | As an alternative, you can use the `gaMeasurementId` param to pass your _Google Analytics measurement id_. 60 | 61 | The `NEXT_PUBLIC_GA_MEASUREMENT_ID` environment variable will take precedence over the `gaMeasurementId` param, so if both are set with different values, the environment variable will override the param. 62 | 63 | ## Scripts 64 | 65 | Use the `GoogleAnalytics` component to load the gtag scripts. You can add it to a [custom App](https://nextjs.org/docs/advanced-features/custom-app) component and this will take care of including the necessary scripts for every page (or you could add it on a per page basis if you need more control): 66 | 67 | ```js 68 | // pages/_app.js 69 | import { GoogleAnalytics } from "nextjs-google-analytics"; 70 | 71 | const App = ({ Component, pageProps }) => { 72 | return ( 73 | <> 74 | 75 | 76 | 77 | ); 78 | }; 79 | 80 | export default App; 81 | ``` 82 | 83 | By default, scripts are loaded using the `afterInteractive` strategy, which means they are injected on the client-side and will run after hydration. 84 | 85 | If you need more control, the component exposes the [strategy](https://nextjs.org/docs/basic-features/script) prop to control how the scripts are loaded: 86 | 87 | ```js 88 | // pages/_app.js 89 | import { GoogleAnalytics } from "nextjs-google-analytics"; 90 | 91 | const App = ({ Component, pageProps }) => { 92 | return ( 93 | <> 94 | 95 | 96 | 97 | ); 98 | }; 99 | 100 | export default App; 101 | ``` 102 | also, you can use alternative to default path for googletagmanager script by 103 | ```js 104 | 105 | ``` 106 | 107 | ## Page views 108 | 109 | To track page views set the `trackPageViews` prop of the `GoogleAnalytics` component to true. 110 | 111 | ```js 112 | // pages/_app.js 113 | import { GoogleAnalytics } from "nextjs-google-analytics"; 114 | 115 | const App = ({ Component, pageProps }) => { 116 | return ( 117 | <> 118 | 119 | 120 | 121 | ); 122 | }; 123 | 124 | export default App; 125 | ``` 126 | 127 | By default it will be trigger on hash changes if `trackPageViews` is enabled, but you can ignore hash changes by providing an object to the `trackPageViews` prop: 128 | 129 | ```js 130 | // pages/_app.js 131 | import { GoogleAnalytics } from "nextjs-google-analytics"; 132 | 133 | const App = ({ Component, pageProps }) => { 134 | return ( 135 | <> 136 | 137 | 138 | 139 | ); 140 | }; 141 | 142 | export default App; 143 | ``` 144 | 145 | As an alternative, you can directly call the `usePageViews` hook inside a [custom App](https://nextjs.org/docs/advanced-features/custom-app) component, **do not set `trackPageViews` prop on the `GoogleAnalytics` component** or set it to false (default): 146 | 147 | ```js 148 | // pages/_app.js 149 | import { GoogleAnalytics, usePageViews } from "nextjs-google-analytics"; 150 | 151 | const App = ({ Component, pageProps }) => { 152 | usePageViews(); // IgnoreHashChange defaults to false 153 | // usePageViews({ ignoreHashChange: true }); 154 | 155 | return ( 156 | <> 157 | {/* or */} 158 | 159 | 160 | ); 161 | }; 162 | 163 | export default App; 164 | ``` 165 | 166 | The module also exports a `pageView` function that you can use if you need more control. 167 | 168 | ## Custom event 169 | 170 | You can use the `event` function to track a custom event: 171 | 172 | ```js 173 | import { useState } from "react"; 174 | import Page from "../components/Page"; 175 | import { event } from "nextjs-google-analytics"; 176 | 177 | export function Contact() { 178 | const [message, setMessage] = useState(""); 179 | 180 | const handleInput = (e) => { 181 | setMessage(e.target.value); 182 | }; 183 | 184 | const handleSubmit = (e) => { 185 | e.preventDefault(); 186 | 187 | event("submit_form", { 188 | category: "Contact", 189 | label: message, 190 | }); 191 | 192 | setState(""); 193 | }; 194 | 195 | return ( 196 | 197 |

This is the Contact page

198 |
199 |