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
3 changes: 2 additions & 1 deletion docs/content/docs/1.guides/2.first-party.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@

### Static Hosting (SSG)

The reverse proxy requires a **server runtime**. For static deployments (`nuxt generate`), the proxy is automatically disabled. Scripts are still bundled and served from your domain, but runtime collection requests (analytics beacons, pixel fires) go directly to third-party servers.

Check warning on line 183 in docs/content/docs/1.guides/2.first-party.md

View workflow job for this annotation

GitHub Actions / lint

Passive voice: "is automatically disabled". Consider rewriting in active voice

Check warning on line 183 in docs/content/docs/1.guides/2.first-party.md

View workflow job for this annotation

GitHub Actions / lint

Passive voice: "is automatically disabled". Consider rewriting in active voice

If you need proxying with static hosting, configure platform-level rewrites manually. The pattern is `/_scripts/p/<domain>/:path*` β†’ `https://<domain>/:path*`:

Expand Down Expand Up @@ -323,7 +323,7 @@

| Category | Scripts |
|----------|---------|
| **Analytics** | [Google Analytics](/scripts/google-analytics), [Plausible](/scripts/plausible-analytics), [Cloudflare Web Analytics](/scripts/cloudflare-web-analytics), [Umami](/scripts/umami-analytics), [Fathom](/scripts/fathom-analytics), [Rybbit](/scripts/rybbit-analytics), [Databuddy](/scripts/databuddy-analytics), [Vercel Analytics](/scripts/vercel-analytics), [Microsoft Clarity](/scripts/clarity), [Hotjar](/scripts/hotjar) |
| **Analytics** | [Google Analytics](/scripts/google-analytics), [Plausible](/scripts/plausible-analytics), [Cloudflare Web Analytics](/scripts/cloudflare-web-analytics), [Umami](/scripts/umami-analytics), [Fathom](/scripts/fathom-analytics), [Rybbit](/scripts/rybbit-analytics), [Databuddy](/scripts/databuddy-analytics), [Microsoft Clarity](/scripts/clarity), [Hotjar](/scripts/hotjar) |
| **Ad Pixels** | [Meta Pixel](/scripts/meta-pixel), [TikTok Pixel](/scripts/tiktok-pixel), [X Pixel](/scripts/x-pixel), [Snapchat Pixel](/scripts/snapchat-pixel), [Reddit Pixel](/scripts/reddit-pixel), [Google AdSense](/scripts/google-adsense) |
| **Video** | [YouTube Player](/scripts/youtube-player), [Vimeo Player](/scripts/vimeo-player) |
| **Utility** | [Intercom](/scripts/intercom), [Gravatar](/scripts/gravatar) |
Expand All @@ -350,6 +350,7 @@
| [Crisp](/scripts/crisp) | SDK loads secondary scripts and CSS at runtime from `client.crisp.chat`. |
| [Mixpanel](/scripts/mixpanel-analytics) | No proxy integration yet. |
| [Bing UET](/scripts/bing-uet) | No proxy integration yet. |
| [Vercel Analytics](/scripts/vercel-analytics) | Collects via relative `/_vercel/insights/*` served by Vercel's edge. Only works on Vercel-hosted deployments; skipped in `nuxt dev`. |
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟑 Minor

Resolve contradictory Vercel Analytics classification in this guide.

Line 353 says Vercel Analytics is bundle-only/no-proxy, but Line 66 still lists it under proxied IP-only scripts. Please align the privacy-tier table to avoid conflicting guidance.

πŸ“ Proposed docs fix
-| **IP only** | IP addresses anonymised to subnet level | Plausible, PostHog, Umami, Fathom, CF Web Analytics, Vercel Analytics, Rybbit, Databuddy, Matomo, Intercom, YouTube, Vimeo, Gravatar, Carbon Ads, Lemon Squeezy, Google AdSense |
+| **IP only** | IP addresses anonymised to subnet level | Plausible, PostHog, Umami, Fathom, CF Web Analytics, Rybbit, Databuddy, Matomo, Intercom, YouTube, Vimeo, Gravatar, Carbon Ads, Lemon Squeezy, Google AdSense |
πŸ€– Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docs/content/docs/1.guides/2.first-party.md` at line 353, The Vercel
Analytics entry is classified inconsistently: update the privacy-tier table so
the Vercel Analytics row/entry (labelled "Vercel Analytics" or path
"/scripts/vercel-analytics") appears only under the bundle-only/no-proxy
category and remove or relocate it from the proxied IP-only scripts list so both
the table and the prose consistently state it is served via Vercel edge and only
works on Vercel (skipped in nuxt dev); ensure the single canonical description
is used in both places (the table row and any list that previously included it).


Bundle-only scripts still benefit from being served as first-party assets (faster loading, no CORS, reduced external connections at page load).

Expand Down Expand Up @@ -461,7 +462,7 @@
| Problem | Fix |
|---------|-----|
| Analytics not tracking | Check DevTools β†’ Network for `/_scripts/p/` requests. Check Nitro server logs for proxy errors |
| Proxy not working on static site | The reverse proxy is automatically disabled for SSG. Use platform rewrites or switch to server mode. See [Static Hosting](#static-hosting-ssg) |

Check warning on line 465 in docs/content/docs/1.guides/2.first-party.md

View workflow job for this annotation

GitHub Actions / lint

Passive voice: "is automatically disabled". Consider rewriting in active voice

Check warning on line 465 in docs/content/docs/1.guides/2.first-party.md

View workflow job for this annotation

GitHub Actions / lint

Passive voice: "is automatically disabled". Consider rewriting in active voice
| Stale script | `rm -rf .nuxt/cache/scripts` and rebuild |
| Build download fails | Set `assets.fallbackOnSrcOnBundleFail: true`{lang="ts"} to fall back to direct loading |
| Debugging | Open Nuxt DevTools β†’ Scripts to see proxy routes and privacy status |
Expand Down
6 changes: 5 additions & 1 deletion docs/content/scripts/vercel-analytics.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ useScriptVercelAnalytics({

### First-Party Mode

First-party mode is auto-enabled for Vercel Analytics. Nuxt bundles the analytics script locally and proxies data collection requests through your server. This prevents ad blockers from blocking analytics and removes sensitive data from third-party requests.
First-party mode is auto-enabled for Vercel Analytics. Nuxt bundles the analytics script locally so ad blockers don't break it. The script collects via a relative `/_vercel/insights/*` path served by Vercel's edge, which is only reachable on Vercel-hosted deployments.

```ts
export default defineNuxtConfig({
Expand All @@ -40,6 +40,10 @@ export default defineNuxtConfig({
})
```

::callout{type="info"}
In `nuxt dev`, the analytics script is skipped to avoid POSTing to an unreachable endpoint. `track` and `pageview` calls become no-ops. Deploy to preview or production to verify events.
::

## Defaults

- **Trigger: Client** Script will load when Nuxt is hydrating to keep web vital metrics accurate.
Expand Down
19 changes: 1 addition & 18 deletions packages/script/src/registry-types.json
Original file line number Diff line number Diff line change
Expand Up @@ -943,18 +943,13 @@
{
"name": "VercelAnalyticsOptions",
"kind": "const",
"code": "export const VercelAnalyticsOptions = object({\n /**\n * The DSN of the project to send events to.\n * Only required when self-hosting or deploying outside of Vercel.\n */\n dsn: optional(string()),\n /**\n * Whether to disable automatic page view tracking on route changes.\n * Set to true if you want to manually call pageview().\n */\n disableAutoTrack: optional(boolean()),\n /**\n * The mode to use for the analytics script.\n * - `auto` - Automatically detect the environment (default)\n * - `production` - Always use production script\n * - `development` - Always use development script (logs to console)\n */\n mode: optional(union([literal('auto'), literal('development'), literal('production')])),\n /**\n * Whether to enable debug logging.\n * Automatically enabled in development/test environments.\n */\n debug: optional(boolean()),\n /**\n * Custom endpoint for data collection.\n * Useful for self-hosted or proxied setups.\n */\n endpoint: optional(string()),\n})"
"code": "export const VercelAnalyticsOptions = object({\n /**\n * The DSN of the project to send events to.\n * Only required when self-hosting or deploying outside of Vercel.\n */\n dsn: optional(string()),\n /**\n * Whether to disable automatic page view tracking on route changes.\n * Set to true if you want to manually call pageview().\n */\n disableAutoTrack: optional(boolean()),\n /**\n * Custom endpoint for data collection.\n * Useful for self-hosted or proxied setups.\n */\n endpoint: optional(string()),\n})"
},
{
"name": "AllowedPropertyValues",
"kind": "type",
"code": "export type AllowedPropertyValues = string | number | boolean | null"
},
{
"name": "VercelAnalyticsMode",
"kind": "type",
"code": "export type VercelAnalyticsMode = 'auto' | 'development' | 'production'"
},
{
"name": "BeforeSendEvent",
"kind": "interface",
Expand Down Expand Up @@ -2214,18 +2209,6 @@
"required": false,
"description": "Whether to disable automatic page view tracking on route changes. Set to true if you want to manually call pageview()."
},
{
"name": "mode",
"type": "'auto' | 'development' | 'production'",
"required": false,
"description": "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)"
},
{
"name": "debug",
"type": "boolean",
"required": false,
"description": "Whether to enable debug logging. Automatically enabled in development/test environments."
},
{
"name": "endpoint",
"type": "string",
Expand Down
2 changes: 1 addition & 1 deletion packages/script/src/registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -306,7 +306,7 @@ export async function registry(resolve?: (path: string) => Promise<string>): Pro
category: 'analytics',
bundle: true,
proxy: {
domains: ['va.vercel-scripts.com', 'vitals.vercel-insights.com'],
domains: ['va.vercel-scripts.com'],
privacy: PRIVACY_IP_ONLY,
},
}),
Expand Down
12 changes: 0 additions & 12 deletions packages/script/src/runtime/registry/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1006,18 +1006,6 @@ export const VercelAnalyticsOptions = object({
* 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.
Expand Down
32 changes: 8 additions & 24 deletions packages/script/src/runtime/registry/vercel-analytics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ export { VercelAnalyticsOptions }

export type AllowedPropertyValues = string | number | boolean | null

export type VercelAnalyticsMode = 'auto' | 'development' | 'production'

export interface BeforeSendEvent {
type: 'pageview' | 'event'
url: string
Expand All @@ -29,7 +27,6 @@ declare global {
interface Window {
va?: (event: string, properties?: unknown) => void
vaq?: [string, unknown?][]
vam?: VercelAnalyticsMode
}
}

Expand Down Expand Up @@ -61,13 +58,8 @@ function parseProperties(
export function useScriptVercelAnalytics<T extends VercelAnalyticsApi>(_options?: VercelAnalyticsInput) {
const beforeSend = _options?.beforeSend
return useRegistryScript<T, typeof VercelAnalyticsOptions>('vercelAnalytics', (options) => {
// import.meta.dev is evaluated at build time, so it determines which script file
// is bundled (script.debug.js vs script.js). The runtime `mode` option (window.vam)
// controls runtime behavior but does not change which script is loaded.
const scriptInput: { 'src': string, 'defer': boolean, 'data-sdkn': string, 'data-dsn'?: string, 'data-disable-auto-track'?: string, 'data-debug'?: string, 'data-endpoint'?: string } = {
'src': import.meta.dev
? 'https://va.vercel-scripts.com/v1/script.debug.js'
: 'https://va.vercel-scripts.com/v1/script.js',
const scriptInput: { 'src': string, 'defer': boolean, 'data-sdkn': string, 'data-dsn'?: string, 'data-disable-auto-track'?: string, 'data-endpoint'?: string } = {
'src': 'https://va.vercel-scripts.com/v1/script.js',
'defer': true,
'data-sdkn': '@nuxt/scripts',
}
Expand All @@ -78,16 +70,17 @@ export function useScriptVercelAnalytics<T extends VercelAnalyticsApi>(_options?
scriptInput['data-disable-auto-track'] = '1'
if (options?.endpoint)
scriptInput['data-endpoint'] = options.endpoint
// Only set data-debug="false" in dev mode to explicitly disable debug logging
if (import.meta.dev && options?.debug === false)
scriptInput['data-debug'] = 'false'

return {
scriptInput,
schema: import.meta.dev ? VercelAnalyticsOptions : undefined,
scriptOptions: {
// Load on client hydration for accurate web vitals
trigger: 'client',
// Vercel Analytics collects via a relative `/_vercel/insights/*` endpoint
// served by Vercel's edge. Outside Vercel (including `nuxt dev`) there is
// no upstream to forward to, so loading would POST to the local origin
// and fail. Default to manual in dev so the script only loads if the
// user explicitly triggers it.
trigger: import.meta.dev ? 'manual' : 'client',
use: () => ({
va: (...args: [string, unknown?]) => window.va?.(...args),
track(name: string, properties?: Record<string, AllowedPropertyValues>) {
Expand All @@ -114,18 +107,9 @@ export function useScriptVercelAnalytics<T extends VercelAnalyticsApi>(_options?
: () => {
if (window.va)
return
// Set up the queue exactly as @vercel/analytics does
window.va = function (...params: [string, unknown?]) {
;(window.vaq = window.vaq || []).push(params)
}
// Set mode β€” auto detects via build environment, explicit sets directly
if (options?.mode === 'auto' || !options?.mode) {
window.vam = import.meta.dev ? 'development' : 'production'
}
else {
window.vam = options.mode
}
// Register beforeSend middleware
if (beforeSend) {
window.va('beforeSend', beforeSend)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,13 @@ function sendPageview() {
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): {{ typeof window !== 'undefined' ? window.vam ?? 'n/a' : 'n/a' }}</div>
<div v-if="eventTracked">
Event tracked!
</div>
Expand Down
3 changes: 1 addition & 2 deletions scripts/generate-sizes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -792,8 +792,7 @@ const CLIENT_INIT: Record<string, string> = {
youtubePlayer: `
window.onYouTubeIframeAPIReady = function() {};`,
vercelAnalytics: `
window.va = function() { (window.vaq = window.vaq || []).push(arguments) };
window.vam = 'production';`,
window.va = function() { (window.vaq = window.vaq || []).push(arguments) };`,
bingUet: `
window.uetq = window.uetq || [];
window.addEventListener('load', function() {
Expand Down
4 changes: 2 additions & 2 deletions test/e2e-dev/first-party.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -871,11 +871,11 @@ describe('first-party privacy stripping', () => {
await page.waitForSelector('#status', { timeout: 5000 }).catch(() => {})
await page.waitForTimeout(2000)

// Vercel Analytics is skipped in dev β€” collection only works on Vercel-hosted deployments
const hasQueue = await page.evaluate(() => typeof window.va === 'function')
expect(hasQueue).toBe(true)
expect(hasQueue).toBe(false)

await page.close()
// No proxy captures expected β€” Vercel sends to relative /_vercel/insights/* paths
}, 30000)

it('posthog', async () => {
Expand Down
4 changes: 0 additions & 4 deletions test/e2e/basic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -441,10 +441,6 @@ describe('third-party-capital', () => {
})
expect(hasQueue).toBe(true)

// Verify window.vam is set (mode auto detects build environment)
const mode = await page.evaluate(() => window.vam)
expect(mode).toBe('production')

// Verify the script tag has correct attributes
const scriptAttrs = await page.evaluate(() => {
const script = document.querySelector('script[data-sdkn="@nuxt/scripts"]')
Expand Down
Loading