Skip to content

Commit

Permalink
feat: support multiple Google tag IDs
Browse files Browse the repository at this point in the history
closes #31
closes #30
  • Loading branch information
johannschopplich committed Feb 9, 2024
1 parent 2c4075c commit aedc6e4
Show file tree
Hide file tree
Showing 7 changed files with 135 additions and 50 deletions.
83 changes: 69 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
![Nuxt Gtag module](./.github/og.jpg)
![Nuxt Google tag module](./.github/og.jpg)

# Nuxt Gtag
# Nuxt Google Tag

[![npm version](https://img.shields.io/npm/v/nuxt-gtag?color=a1b858&label=)](https://www.npmjs.com/package/nuxt-gtag)

[Nuxt](https://nuxt.com) module to integrate [Google Analytics 4](https://developers.google.com/analytics/devguides/collection/ga4?hl=en).
[Google Tag](https://developers.google.com/tag-platform/gtagjs?hl=en) integration for [Nuxt](https://nuxt.com) with support for [Google Analytics 4](https://developers.google.com/analytics/devguides/collection/ga4?hl=en) with consent management, Google Ads and more.

## Features

- 🌻 No dependencies except Google's `gtag.js`
- 🛍️ For Google Analytics 4, Google Ads and other products
- 🤝 Manual [consent management](#consent-management) for GDPR compliance
- 🔢 Supports [multiple tag IDs](#multiple-google-tags)
- 📯 Track events manually with [composables](#composables)
- 🏷️ Fully typed `gtag.js` API
- 🦾 SSR-ready
Expand All @@ -30,7 +32,7 @@ yarn add -D nuxt-gtag

## Basic Usage

Add `nuxt-gtag` to the `modules` section of your Nuxt configuration and provide your Google Analytics measurement ID.
Add `nuxt-gtag` to the `modules` section of your Nuxt configuration and provide your Google tag ID.

```ts
// `nuxt.config.ts`
Expand All @@ -43,7 +45,7 @@ export default defineNuxtConfig({
})
```

Done! Google Analytics will now run in your application's client.
Done! Google tag will now run in your application's client.

> [!NOTE]
> Ensure that the **Enhanced measurement** feature is enabled to allow `gtag.js` to automatically track page changes based on browser history events in Nuxt.
Expand All @@ -55,6 +57,52 @@ Done! Google Analytics will now run in your application's client.
> 3. Click on your web data stream.
> 4. Next, toggle the switch button near “Enhanced measurement”.
### Multiple Google Tags

If you want to send data to multiple destinations, you can add more than one Google tag ID to your Nuxt configuration in the `tags` module option.

The following example shows how to load a second Google tag that is connected to a Floodlight destination. To send data to Floodlight (tag ID `DC-ZZZZZZ`), add another config command after initializing the first Google tag (tag ID `GT-XXXXXX`):

```ts
// `nuxt.config.ts`
export default defineNuxtConfig({
modules: ['nuxt-gtag'],

gtag: {
tags: [
'GT-XXXXXX', // Google Ads and GA4
'DC-ZZZZZZ' // Floodlight
]
}
})
```

Or use the object syntax to initialize multiple tags with different configurations:

```ts
// `nuxt.config.ts`
export default defineNuxtConfig({
modules: ['nuxt-gtag'],

gtag: {
tags: [
{
id: 'GT-XXXXXX',
config: {
page_title: 'My Custom Page Title'
}
},
{
id: 'DC-ZZZZZZ',
config: {
page_title: 'My Custom Page Title'
}
}
]
}
})
```

## Configuration

All [supported module options](#module-options) can be configured using the `gtag` key in your Nuxt configuration:
Expand All @@ -64,26 +112,32 @@ export default defineNuxtConfig({
modules: ['nuxt-gtag'],

gtag: {
// The Google Analytics 4 property ID to use for tracking
// The Google tag ID to use for tracking
id: 'G-XXXXXXXXXX',
// Additional configuration for the Google Analytics 4 property
// Additional configuration for your tag ID
config: {
page_title: 'My Custom Page Title'
}
},

// Or use the tags option to send data to multiple destinations
tags: [
'GT-XXXXXX', // Google Ads and GA4
'DC-ZZZZZZ' // Floodlight
]
}
})
```

### Runtime Config

Instead of hard-coding your measurement ID in your Nuxt configuration, you can set your desired option in your project's `.env` file, leveraging [automatically replaced public runtime config values](https://nuxt.com/docs/api/configuration/nuxt-config#runtimeconfig) by matching environment variables at runtime.
Instead of hard-coding your Google tag ID in your Nuxt configuration, you can set your desired option in your project's `.env` file, leveraging [automatically replaced public runtime config values](https://nuxt.com/docs/api/configuration/nuxt-config#runtimeconfig) by matching environment variables at runtime.

```ini
# Overwrites the `gtag.id` module option
NUXT_PUBLIC_GTAG_ID=G-XXXXXXXXXX
```

With this setup, you can omit the `gtag` key in your Nuxt configuration if you only intend to set the measurement ID.
With this setup, you can omit the `gtag` key in your Nuxt configuration if you only intend to set the Google tag ID.

### Consent Management

Expand Down Expand Up @@ -114,7 +168,7 @@ const { gtag, grantConsent, revokeConsent } = useGtag()
</template>
```

You can even leave the measurement ID in your Nuxt config blank and set it dynamically later in your application by passing your ID as the first argument to `grantConsent`. This is especially useful if you want to use a custom ID for each user or if your app manages multiple tenants.
You can even leave the Google tag ID in your Nuxt config blank and set it dynamically later in your application by passing your ID as the first argument to `grantConsent`. This is especially useful if you want to use a custom ID for each user or if your app manages multiple tenants.

```ts
const { gtag, grantConsent, revokeConsent } = useGtag()
Expand All @@ -128,8 +182,9 @@ function acceptTracking() {

| Option | Type | Default | Description |
| --- | --- | --- | --- |
| `id` | `string` | `undefined` | The Google Analytics measurement ID. |
| `config` | `Record<string, any>` | `{}` | The [configuration parameters](https://developers.google.com/analytics/devguides/collection/ga4/reference/config) to be passed to `gtag.js` on initialization. |
| `id` | `string` | `undefined` | The Google tag ID to use for tracking. |
| `tags` | `string[] \| GoogleTagOptions[]` | `[]` | Google tag IDs to send data to multiple destinations. |
| `config` | `Record<string, any>` | `undefined` | The [configuration parameters](https://developers.google.com/analytics/devguides/collection/ga4/reference/config) to be passed to `gtag.js` on initialization. |
| `initialConsent` | `boolean` | `true` | Whether to initially consent to tracking. |
| `loadingStrategy` | `'async' \| 'defer'` | `'defer'` | The loading strategy to be used for the `gtag.js` script. |
| `url` | `string` | `'https://www.googletagmanager.com/gtag/js'` | The URL to the `gtag.js` script. Use this option to load the script from a custom URL. |
Expand Down Expand Up @@ -213,7 +268,7 @@ gtag('event', 'screen_view', {

If you want to manually manage consent, i.e. for GDPR compliance, you can use the `grantConsent` method to grant the consent. Make sure to set `initialConsent` to `false` in the module options beforehand.

This function accepts an optional ID in case you want to initialize a custom Gtag ID and haven't set it in the module options.
This function accepts an optional ID in case you want to initialize a custom Google tag ID and haven't set it in the module options.

```ts
const { grantConsent } = useGtag()
Expand Down
7 changes: 3 additions & 4 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"type": "module",
"version": "1.1.2",
"packageManager": "pnpm@8.15.1",
"description": "Nuxt 3 module to natively integrate Google Analytics",
"description": "Natively integrates Google Tag into Nuxt",
"author": "Johann Schopplich <pkg@johannschopplich.com>",
"license": "MIT",
"homepage": "https://github.com/johannschopplich/nuxt-gtag#readme",
Expand All @@ -13,12 +13,11 @@
},
"bugs": "https://github.com/johannschopplich/nuxt-gtag/issues",
"keywords": [
"nuxt",
"nuxt3",
"analytics",
"google",
"gtag",
"gtm",
"analytics"
"nuxt"
],
"exports": {
".": {
Expand Down
40 changes: 31 additions & 9 deletions src/module.ts
Original file line number Diff line number Diff line change
@@ -1,37 +1,48 @@
import { defu } from 'defu'
import { addImports, addPlugin, createResolver, defineNuxtModule } from '@nuxt/kit'
import { name, version } from '../package.json'
import type { GoogleTagOptions } from './runtime/types'

export interface ModuleOptions {
/**
* The Google Analytics 4 property ID to use for tracking.
* The Google tag ID to initialize.
*
* @default undefined
*/
id?: string

/**
* Additional configuration for the Google Analytics 4 property.
* The Google tags to initialize.
*
* @remarks
* Will be set when initializing `gtag` with the `config` command.
* Each item can be a string or an object with `id` and `config` properties. The latter is useful especially when you want to set additional configuration for the Google tag ID.
*
* @default undefined
*/
tags?: string[] | GoogleTagOptions[]

/**
* Additional configuration for the Google tag ID to be set when initializing the tag ID with the `config` command.
*
* @remarks
* Does only apply when `id` is set or the `ids` array contains strings.
*
* @default undefined
*/
config?: Record<string, any>

/**
* Whether to initially consent to tracking.
* Whether to initially consent to tracking if the tag ID is for Google Analytics.
*
* @remarks
* If set to `true`, the Google Analytics 4 script will be loaded immediately.
* If set to `true`, the Google tag ID script will be loaded immediately.
*
* @default true
*/
initialConsent?: boolean

/**
* Whether to load the Google Analytics 4 script asynchronously or defer its loading.
* Whether to load the Google tag ID script asynchronously or defer its loading.
*
* @remarks
* If set to `async`, the script will be loaded asynchronously.
Expand All @@ -42,7 +53,7 @@ export interface ModuleOptions {
loadingStrategy?: 'async' | 'defer'

/**
* The URL to load the Google Analytics 4 script from.
* The URL to load the Google tag script from.
*
* @remarks
* Useful if you want to proxy the script through your own server.
Expand All @@ -52,6 +63,10 @@ export interface ModuleOptions {
url?: string
}

export interface ResolvedModuleOptions extends ModuleOptions {
tags: GoogleTagOptions[]
}

export default defineNuxtModule<ModuleOptions>({
meta: {
name,
Expand All @@ -62,15 +77,22 @@ export default defineNuxtModule<ModuleOptions>({
},
},
defaults: {
id: '',
config: {},
tags: [],
initialConsent: true,
loadingStrategy: 'defer',
url: 'https://www.googletagmanager.com/gtag/js',
},
setup(options, nuxt) {
const { resolve } = createResolver(import.meta.url)

// Normalize options
options.tags = options.tags!.filter(Boolean).map(
i => typeof i === 'string' ? { id: i } : i,
)

if (options.id)
options.tags.unshift({ id: options.id, config: options.config })

// Add module options to public runtime config
nuxt.options.runtimeConfig.public.gtag = defu(
nuxt.options.runtimeConfig.public.gtag as Required<ModuleOptions>,
Expand Down
24 changes: 12 additions & 12 deletions src/runtime/composables/useGtag.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { withQuery } from 'ufo'
import { disableGtag, enableGtag, gtag, initGtag } from '../gtag'
import type { ModuleOptions } from '../../module'
import type { ResolvedModuleOptions } from '../../module'
import type { Gtag, UseGtagConsentOptions } from '../types'
import { useHead, useRuntimeConfig } from '#imports'

export function useGtag() {
const { id: defaultId, config, url } = useRuntimeConfig().public.gtag as Required<ModuleOptions>
const { tags, url } = useRuntimeConfig().public.gtag as Required<ResolvedModuleOptions>

let _gtag: Gtag
// Return a noop function if this composable is called on the server.
Expand All @@ -17,41 +17,41 @@ export function useGtag() {
const setConsent = (options: UseGtagConsentOptions) => {
if (process.client) {
const hasConsent = options.hasConsent ?? true
const id = options.id || defaultId
const tag = tags?.find(tag => tag.id === options.id) ?? tags?.[0] ?? { id: options.id }

if (!id) {
console.error('[nuxt-gtag] Missing Google Analytics ID')
if (!tag) {
console.error('[nuxt-gtag] Missing Google tag ID')
return
}

if (!hasConsent) {
disableGtag(id)
disableGtag(tag.id)
return
}

// Initialize `dataLayer` if the client plugin didn't initialize it
// (because no ID was provided in the module options).
if (!window.dataLayer)
initGtag({ id, config })
initGtag({ tags: [tag] })

// If the `dataLayer` has more than two items
// it is considered to be initialized.
if (window.dataLayer!.length > 2) {
// Re-enable Google Analytics if it was disabled before.
enableGtag(id)
enableGtag(tag.id)
return
}

// Inject the Google Analytics script.
// Inject the Google tag script.
useHead({
script: [{ src: withQuery(url, { id }) }],
script: [{ src: withQuery(url, { id: tag.id }) }],
})
}
}

const grantConsent = (
/**
* In case you want to initialize a custom Gtag ID. Make sure to set
* In case you want to initialize a custom Google tag ID. Make sure to set
* `initialConsent` to `false` in the module options beforehand.
*/
id?: string,
Expand All @@ -61,7 +61,7 @@ export function useGtag() {

const revokeConsent = (
/**
* In case you want to initialize a custom Gtag ID. Make sure to set
* In case you want to initialize a custom Google tag ID. Make sure to set
* `initialConsent` to `false` in the module options beforehand.
*/
id?: string,
Expand Down
14 changes: 9 additions & 5 deletions src/runtime/gtag.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
import type { GoogleTagOptions } from './types'

// eslint-disable-next-line unused-imports/no-unused-vars
export function gtag(command: string, ...args: any[]) {
// eslint-disable-next-line prefer-rest-params
window.dataLayer?.push(arguments)
}

/**
* Initialize the Google Analytics script.
* Initialize the Google tag.
*/
export function initGtag({ id, config }: { id: string, config: any }) {
export function initGtag({ tags }: { tags: GoogleTagOptions[] }) {
window.dataLayer = window.dataLayer || []

gtag('js', new Date())
gtag('config', id, config)
for (const tag of tags)
gtag('config', tag.id, tag.config)
}

/**
* Disable Google Analytics.
* Disable the Google tag if it is a Google Analytics property.
*
* The Google tag (gtag.js) library includes a `window['ga-disable-MEASUREMENT_ID']`
* @remarks
* The Google tag library includes a `window['ga-disable-GA_MEASUREMENT_ID']`
* property that, when set to `true`, turns off the Google tag from sending data.
*
* @see https://developers.google.com/analytics/devguides/collection/gtagjs/user-opt-out
Expand Down

0 comments on commit aedc6e4

Please sign in to comment.