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
194 changes: 194 additions & 0 deletions docs/content/scripts/analytics/vercel-analytics.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
---
title: Vercel Analytics
description: Use Vercel Analytics in your Nuxt app.
links:
- label: Source
icon: i-simple-icons-github
to: https://github.com/nuxt/scripts/blob/main/src/runtime/registry/vercel-analytics.ts
size: xs
- label: Vercel Analytics
icon: i-simple-icons-vercel
to: https://vercel.com/docs/analytics
size: xs
---

[Vercel Analytics](https://vercel.com/docs/analytics) provides lightweight, privacy-friendly web analytics for your Nuxt app. It tracks page views and custom events with zero configuration when deployed on Vercel.

The simplest way to load Vercel Analytics globally in your Nuxt App is to use Nuxt config. Alternatively you can directly
use the [useScriptVercelAnalytics](#usescriptvercelanalytics) composable.

## Loading Globally

If you'd like to avoid loading the analytics in development, you can use the [Environment overrides](https://nuxt.com/docs/getting-started/configuration#environment-overrides) in your Nuxt config.

::code-group

```ts [Always enabled]
export default defineNuxtConfig({
scripts: {
registry: {
vercelAnalytics: true,
}
}
})
```

```ts [Production only]
export default defineNuxtConfig({
$production: {
scripts: {
registry: {
vercelAnalytics: true,
}
}
}
})
```

```ts [Non-Vercel deployment]
export default defineNuxtConfig({
scripts: {
registry: {
vercelAnalytics: {
dsn: 'YOUR_DSN',
}
}
}
})
```

::

### First-Party Mode

When `scripts.firstParty` is enabled, the analytics script is bundled locally and data collection requests are proxied through your server. This prevents ad blockers from blocking analytics and removes sensitive data from third-party requests.

```ts
export default defineNuxtConfig({
scripts: {
firstParty: true,
registry: {
vercelAnalytics: true,
}
}
})
```

## useScriptVercelAnalytics

The `useScriptVercelAnalytics` composable lets you have fine-grain control over when and how Vercel Analytics is loaded on your site.

```ts
function useScriptVercelAnalytics<T extends VercelAnalyticsApi>(_options?: VercelAnalyticsInput) {}
```

Please follow the [Registry Scripts](/docs/guides/registry-scripts) guide to learn more about advanced usage.

The composable comes with the following defaults:
- **Trigger: Client** Script will load when Nuxt is hydrating to keep web vital metrics accurate.

### VercelAnalyticsInput

```ts
export const VercelAnalyticsOptions = object({
/**
* The DSN of the project to send events to.
* Only required when self-hosting or deploying outside of Vercel.
*/
dsn: optional(string()),
/**
* Whether to disable automatic page view tracking on route changes.
* Set to true if you want to manually call pageview().
*/
disableAutoTrack: optional(boolean()),
/**
* The mode to use for the analytics script.
* - `auto` - Automatically detect the environment (default)
* - `production` - Always use production script
* - `development` - Always use development script (logs to console)
*/
mode: optional(union([literal('auto'), literal('development'), literal('production')])),
/**
* Whether to enable debug logging.
* Automatically enabled in development/test environments.
*/
debug: optional(boolean()),
/**
* Custom endpoint for data collection.
* Useful for self-hosted or proxied setups.
*/
endpoint: optional(string()),
})
```

You can also pass a `beforeSend` callback to filter or modify events before they are sent:

```ts
type BeforeSendEvent = { type: 'pageview' | 'event', url: string }
type BeforeSend = (event: BeforeSendEvent) => BeforeSendEvent | null
```

### VercelAnalyticsApi

```ts
export interface VercelAnalyticsApi {
va: (event: string, properties?: unknown) => void
track: (name: string, properties?: Record<string, AllowedPropertyValues>) => void
pageview: (options?: { route?: string | null, path?: string }) => void
}
```

::callout{icon="i-heroicons-light-bulb"}
The `track()` method validates properties at runtime β€” nested objects are silently stripped in production, and throw an error in development.
::

## Example

Loading Vercel Analytics through `app.vue` when Nuxt is ready.

```vue [app.vue]
<script setup lang="ts">
const { proxy } = useScriptVercelAnalytics({
scriptOptions: {
trigger: 'onNuxtReady',
},
})

// Track a custom event
proxy.track('signup', { plan: 'pro' })
</script>
```

### Manual Tracking

If you want full control over what gets tracked, disable automatic tracking and call `track` / `pageview` manually.

```vue [app.vue]
<script setup lang="ts">
const { proxy } = useScriptVercelAnalytics({
disableAutoTrack: true,
})

// Track custom event
proxy.track('purchase', { product: 'widget', price: 9.99 })

// Manual pageview
proxy.pageview({ path: '/custom-page' })
</script>
```

### beforeSend

Use `beforeSend` to filter or modify events before they reach Vercel. Return `null` to cancel an event.

```vue [app.vue]
<script setup lang="ts">
const { proxy } = useScriptVercelAnalytics({
beforeSend(event) {
// Ignore admin pages
if (event.url.includes('/admin')) return null
return event
},
})
</script>
```
83 changes: 83 additions & 0 deletions playground/pages/third-parties/vercel-analytics/nuxt-scripts.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
<script lang="ts" setup>
import { ref, useHead } from '#imports'

useHead({
title: 'Vercel Analytics',
})

const beforeSendLog = ref<string[]>([])

const { proxy, status } = useScriptVercelAnalytics({
endpoint: '/custom/collect',
beforeSend(event) {
beforeSendLog.value.push(`${event.type}: ${event.url}`)
return event
},
scriptOptions: {
trigger: 'onNuxtReady',
},
})

const eventTracked = ref(false)
const pageviewSent = ref(false)

function trackEvent() {
proxy.track('button_click', {
button: 'demo',
page: '/third-parties/vercel-analytics',
})
eventTracked.value = true
}

function trackNested() {
proxy.track('nested_test', {
valid: 'yes',
nested: { should: 'be stripped' } as any,
})
}

function sendPageview() {
proxy.pageview({ path: '/test-page', route: '/[slug]' })
pageviewSent.value = true
}

function dumpQueue() {
// eslint-disable-next-line no-console
console.log('window.vaq:', JSON.stringify(window.vaq, null, 2))
// eslint-disable-next-line no-console
console.log('window.vam:', window.vam)
}
</script>

<template>
<div class="flex flex-col gap-4 p-4">
<ClientOnly>
<div>status: {{ status }}</div>
<div>mode (window.vam): {{ $window?.vam ?? 'n/a' }}</div>
<div v-if="eventTracked">
Event tracked!
</div>
<div v-if="pageviewSent">
Pageview sent!
</div>
<div v-if="beforeSendLog.length">
beforeSend calls: {{ beforeSendLog }}
</div>
</ClientOnly>

<div class="flex gap-2">
<button @click="trackEvent">
Track Event
</button>
<button @click="trackNested">
Track Nested (stripped in prod)
</button>
<button @click="sendPageview">
Send Pageview
</button>
<button @click="dumpQueue">
Dump Queue (console)
</button>
</div>
</div>
</template>
8 changes: 8 additions & 0 deletions src/proxy-configs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,14 @@ function buildProxyConfig(collectPrefix: string) {
[`${collectPrefix}/hotjar-insights/**`]: { proxy: 'https://insights.hotjar.com/**' },
},
},

vercelAnalytics: {
// No rewrite needed β€” the Vercel Analytics script uses relative paths
// for data collection (/_vercel/insights/*), not absolute URLs to va.vercel-scripts.com
routes: {
[`${collectPrefix}/vercel/**`]: { proxy: 'https://va.vercel-scripts.com/**' },
},
},
} satisfies Record<string, ProxyConfig>
}

Expand Down
11 changes: 11 additions & 0 deletions src/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,17 @@ export async function registry(resolve?: (path: string, opts?: ResolvePathOption
from: await resolve('./runtime/registry/cloudflare-web-analytics'),
},
},
{
label: 'Vercel Analytics',
src: 'https://va.vercel-scripts.com/v1/script.js',
proxy: 'vercelAnalytics',
category: 'analytics',
logo: `<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 512 512"><path d="M256 48L496 464H16z" fill="currentColor"/></svg>`,
import: {
name: 'useScriptVercelAnalytics',
from: await resolve('./runtime/registry/vercel-analytics'),
},
},
{
label: 'PostHog',
src: false,
Expand Down
Loading
Loading