Skip to content
Open
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
118 changes: 111 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,26 +53,122 @@ That's it! You can now use Nuxt UTM in your Nuxt app ✨

## Usage

You can use `useNuxtUTM` composable to access the UTM object:
### Configuration

You can configure the module by passing options in your `nuxt.config.ts`:

```js
export default defineNuxtConfig({
modules: ['nuxt-utm'],
utm: {
trackingEnabled: true, // defaults to true - initial tracking state
},
})
```

#### Options

- `trackingEnabled`: Boolean (default: `true`) - Sets the initial state for UTM tracking. This can be changed at runtime.

### Runtime Tracking Control

The module provides runtime control over tracking, perfect for implementing cookie consent banners or user privacy preferences.

#### Using the Composable

```vue
<script setup>
const utm = useNuxtUTM()

// The composable returns:
// - data: Reactive array of collected UTM data
// - trackingEnabled: Reactive boolean indicating if tracking is active
// - enableTracking(): Enable UTM tracking
// - disableTracking(): Disable UTM tracking
// - clearData(): Clear all stored UTM data
</script>
```

> Remember: You don't need to import the composable because nuxt imports it automatically.
#### Example: Cookie Banner Integration

```vue
<template>
<div v-if="showBanner" class="cookie-banner">
<p>We use tracking to improve your experience.</p>
<button @click="acceptTracking">Accept</button>
<button @click="rejectTracking">Reject</button>
</div>
</template>

Alternatively, you can get the UTM information through the Nuxt App with the following instructions:
<script setup>
import { ref } from 'vue'
const utm = useNuxtUTM()
const showBanner = ref(!utm.trackingEnabled.value)

const acceptTracking = () => {
utm.enableTracking()
showBanner.value = false
}

const rejectTracking = () => {
utm.disableTracking()
utm.clearData() // Optional: clear any existing data
showBanner.value = false
}
</script>
```

#### Privacy Controls

```vue
<template>
<div class="privacy-settings">
<h3>Privacy Settings</h3>
<label>
<input
type="checkbox"
:checked="utm.trackingEnabled.value"
@change="toggleTracking"
/>
Enable UTM tracking
</label>
<button @click="utm.clearData" v-if="utm.data.value.length > 0">
Clear tracking data ({{ utm.data.value.length }} entries)
</button>
</div>
</template>

<script setup>
const utm = useNuxtUTM()

const toggleTracking = (event) => {
if (event.target.checked) {
utm.enableTracking()
} else {
utm.disableTracking()
}
}
</script>
```

### Accessing UTM Data

You can use `useNuxtUTM` composable to access the UTM data:

```vue
<script setup>
import { useNuxtApp } from 'nuxt/app'
const { $utm } = useNuxtApp()
const utm = useNuxtUTM()

// Access the collected data
console.log(utm.data.value)
</script>
```

Regardless of the option you choose to use the module, the `utm' object will contain an array of UTM parameters collected for use. Each element in the array represents a set of UTM parameters collected from a URL visit, and is structured as follows
> Remember: You don't need to import the composable because Nuxt imports it automatically.

### Data Structure

The `data` property contains an array of UTM parameters collected. Each element in the array represents a set of UTM parameters collected from a URL visit, and is structured as follows

```json
[
Expand Down Expand Up @@ -104,7 +200,15 @@ Regardless of the option you choose to use the module, the `utm' object will con
]
```

In the `$utm` array, each entry provides a `timestamp` indicating when the UTM parameters were collected, the `utmParams` object containing the UTM parameters, `additionalInfo` object with more context about the visit, and a `sessionId` to differentiate visits in different sessions.
Each entry provides a `timestamp` indicating when the UTM parameters were collected, the `utmParams` object containing the UTM parameters, `additionalInfo` object with more context about the visit, and a `sessionId` to differentiate visits in different sessions.

### Key Features

- **Runtime Control**: Enable/disable tracking dynamically based on user consent
- **Privacy Friendly**: Respects user preferences and provides clear data management
- **Persistent Preferences**: Tracking preferences are saved and persist across sessions
- **Data Clearing**: Ability to completely remove all collected data
- **Session Management**: Automatically manages sessions to avoid duplicate tracking

## Development

Expand Down
140 changes: 138 additions & 2 deletions playground/app.vue
Original file line number Diff line number Diff line change
@@ -1,10 +1,146 @@
<template>
<div>Nuxt 3 UTM module playground!</div>
<pre>{{ utm }}</pre>
<div class="container">
<h1>Nuxt UTM Module Playground</h1>

<div class="controls">
<h2>Tracking Controls</h2>
<p>
Tracking is currently:
<strong :class="{ enabled: utm.trackingEnabled.value, disabled: !utm.trackingEnabled.value }">
{{ utm.trackingEnabled.value ? 'ENABLED' : 'DISABLED' }}
</strong>
</p>

<div class="buttons">
<button
:disabled="utm.trackingEnabled.value"
@click="utm.enableTracking"
>
Enable Tracking
</button>
<button
:disabled="!utm.trackingEnabled.value"
@click="utm.disableTracking"
>
Disable Tracking
</button>
<button
class="danger"
@click="utm.clearData"
>
Clear All Data
</button>
</div>

<div class="info">
<p>Try visiting with UTM parameters:</p>
<a href="/?utm_source=test&utm_medium=demo&utm_campaign=playground">
Add UTM params to URL
</a>
</div>
</div>

<div class="data">
<h2>Collected UTM Data ({{ utm.data.value.length }} entries)</h2>
<pre>{{ utm.data.value }}</pre>
</div>
</div>
</template>

<script setup>
import { useNuxtUTM } from '#imports'

const utm = useNuxtUTM()
</script>

<style scoped>
.container {
max-width: 1200px;
margin: 0 auto;
padding: 2rem;
font-family: system-ui, -apple-system, sans-serif;
}

h1 {
color: #00dc82;
margin-bottom: 2rem;
}

.controls {
background: #f5f5f5;
padding: 1.5rem;
border-radius: 8px;
margin-bottom: 2rem;
}

.buttons {
display: flex;
gap: 1rem;
margin: 1rem 0;
}

button {
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
background: #00dc82;
color: white;
cursor: pointer;
font-size: 1rem;
}

button:hover:not(:disabled) {
background: #00c76d;
}

button:disabled {
opacity: 0.5;
cursor: not-allowed;
}

button.danger {
background: #dc3545;
}

button.danger:hover {
background: #c82333;
}

.enabled {
color: #28a745;
}

.disabled {
color: #dc3545;
}

.info {
margin-top: 1rem;
padding-top: 1rem;
border-top: 1px solid #ddd;
}

.info a {
color: #00dc82;
text-decoration: none;
}

.info a:hover {
text-decoration: underline;
}

.data {
background: #f8f9fa;
padding: 1.5rem;
border-radius: 8px;
}

pre {
background: white;
padding: 1rem;
border-radius: 4px;
overflow-x: auto;
max-height: 400px;
overflow-y: auto;
}
</style>
9 changes: 8 additions & 1 deletion playground/nuxt.config.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
export default defineNuxtConfig({
modules: ['../src/module'],
devtools: { enabled: true },
utm: {},
app: {
head: {
title: 'Nuxt UTM Playground',
},
},
utm: {
trackingEnabled: true,
},
})
19 changes: 11 additions & 8 deletions src/module.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { defineNuxtModule, addPlugin, addImports, createResolver } from '@nuxt/kit'

// Module options TypeScript interface definition
/* eslint-disable @typescript-eslint/no-empty-object-type */
export interface ModuleOptions {}
/* eslint-enable @typescript-eslint/no-empty-object-type */
export interface ModuleOptions {
trackingEnabled?: boolean
}

export default defineNuxtModule<ModuleOptions>({
meta: {
Expand All @@ -13,12 +12,16 @@ export default defineNuxtModule<ModuleOptions>({
nuxt: '^3.0.0 || ^4.0.0',
},
},
// Default configuration options of the Nuxt module
defaults: {},
setup() {
defaults: {
trackingEnabled: true,
},
setup(options, nuxt) {
const resolver = createResolver(import.meta.url)

// Do not add the extension since the `.ts` will be transpiled to `.mjs` after `npm run prepack`
nuxt.options.runtimeConfig.public.utm = {
trackingEnabled: options.trackingEnabled ?? true,
}

addPlugin(resolver.resolve('./runtime/plugin'))
addImports({
name: 'useNuxtUTM',
Expand Down
21 changes: 19 additions & 2 deletions src/runtime/composables.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,23 @@
import type { Ref } from 'vue'
import type { DataObject } from 'nuxt-utm'
import { useNuxtApp } from '#imports'

export const useNuxtUTM = () => {
export interface UseNuxtUTMReturn {
data: Readonly<Ref<readonly DataObject[]>>
trackingEnabled: Readonly<Ref<boolean>>
enableTracking: () => void
disableTracking: () => void
clearData: () => void
}

export const useNuxtUTM = (): UseNuxtUTMReturn => {
const nuxtApp = useNuxtApp()
return nuxtApp.$utm

return {
data: nuxtApp.$utm,
trackingEnabled: nuxtApp.$utmTrackingEnabled,
enableTracking: nuxtApp.$utmEnableTracking,
disableTracking: nuxtApp.$utmDisableTracking,
clearData: nuxtApp.$utmClearData,
}
}
Loading