From c447f12ac9b9de4684f4646bf3b1c45161a4dc94 Mon Sep 17 00:00:00 2001 From: Harlan Wilton Date: Thu, 16 Apr 2026 02:06:32 +1000 Subject: [PATCH] fix(vercel-analytics): skip script in dev to avoid failed insights POSTs Vercel Analytics collects via a relative `/_vercel/insights/*` endpoint served by Vercel's edge. Outside a Vercel deployment (including `nuxt dev`) there's no upstream, so the script POSTs to the local origin and 404s. Default to manual trigger in dev so the script only loads if explicitly triggered, and drop the unused `mode`/`debug` options that leaked the old debug-script path. --- docs/content/docs/1.guides/2.first-party.md | 3 +- docs/content/scripts/vercel-analytics.md | 6 +++- packages/script/src/registry-types.json | 19 +---------- packages/script/src/registry.ts | 2 +- .../script/src/runtime/registry/schemas.ts | 12 ------- .../src/runtime/registry/vercel-analytics.ts | 32 +++++-------------- .../vercel-analytics/nuxt-scripts.vue | 3 -- scripts/generate-sizes.ts | 3 +- test/e2e-dev/first-party.test.ts | 4 +-- test/e2e/basic.test.ts | 4 --- 10 files changed, 20 insertions(+), 68 deletions(-) diff --git a/docs/content/docs/1.guides/2.first-party.md b/docs/content/docs/1.guides/2.first-party.md index 7130009f..1e631ef7 100644 --- a/docs/content/docs/1.guides/2.first-party.md +++ b/docs/content/docs/1.guides/2.first-party.md @@ -323,7 +323,7 @@ These scripts are downloaded at build time, served from your domain, and have th | 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) | @@ -350,6 +350,7 @@ These scripts are served from your domain but their runtime requests still go di | [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`. | Bundle-only scripts still benefit from being served as first-party assets (faster loading, no CORS, reduced external connections at page load). diff --git a/docs/content/scripts/vercel-analytics.md b/docs/content/scripts/vercel-analytics.md index 2498d408..d9c501d9 100644 --- a/docs/content/scripts/vercel-analytics.md +++ b/docs/content/scripts/vercel-analytics.md @@ -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({ @@ -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. diff --git a/packages/script/src/registry-types.json b/packages/script/src/registry-types.json index f10d1178..afb442b0 100644 --- a/packages/script/src/registry-types.json +++ b/packages/script/src/registry-types.json @@ -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", @@ -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", diff --git a/packages/script/src/registry.ts b/packages/script/src/registry.ts index 7168deb1..a3f89583 100644 --- a/packages/script/src/registry.ts +++ b/packages/script/src/registry.ts @@ -306,7 +306,7 @@ export async function registry(resolve?: (path: string) => Promise): Pro category: 'analytics', bundle: true, proxy: { - domains: ['va.vercel-scripts.com', 'vitals.vercel-insights.com'], + domains: ['va.vercel-scripts.com'], privacy: PRIVACY_IP_ONLY, }, }), diff --git a/packages/script/src/runtime/registry/schemas.ts b/packages/script/src/runtime/registry/schemas.ts index 796c6ef4..b79336c0 100644 --- a/packages/script/src/runtime/registry/schemas.ts +++ b/packages/script/src/runtime/registry/schemas.ts @@ -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. diff --git a/packages/script/src/runtime/registry/vercel-analytics.ts b/packages/script/src/runtime/registry/vercel-analytics.ts index da1466b4..694928f5 100644 --- a/packages/script/src/runtime/registry/vercel-analytics.ts +++ b/packages/script/src/runtime/registry/vercel-analytics.ts @@ -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 @@ -29,7 +27,6 @@ declare global { interface Window { va?: (event: string, properties?: unknown) => void vaq?: [string, unknown?][] - vam?: VercelAnalyticsMode } } @@ -61,13 +58,8 @@ function parseProperties( export function useScriptVercelAnalytics(_options?: VercelAnalyticsInput) { const beforeSend = _options?.beforeSend return useRegistryScript('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', } @@ -78,16 +70,17 @@ export function useScriptVercelAnalytics(_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) { @@ -114,18 +107,9 @@ export function useScriptVercelAnalytics(_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) } diff --git a/playground/pages/third-parties/vercel-analytics/nuxt-scripts.vue b/playground/pages/third-parties/vercel-analytics/nuxt-scripts.vue index b5fa2468..8101db9c 100644 --- a/playground/pages/third-parties/vercel-analytics/nuxt-scripts.vue +++ b/playground/pages/third-parties/vercel-analytics/nuxt-scripts.vue @@ -44,8 +44,6 @@ 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) } @@ -53,7 +51,6 @@ function dumpQueue() {
status: {{ status }}
-
mode (window.vam): {{ typeof window !== 'undefined' ? window.vam ?? 'n/a' : 'n/a' }}
Event tracked!
diff --git a/scripts/generate-sizes.ts b/scripts/generate-sizes.ts index dc48059f..dc714b03 100644 --- a/scripts/generate-sizes.ts +++ b/scripts/generate-sizes.ts @@ -792,8 +792,7 @@ const CLIENT_INIT: Record = { 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() { diff --git a/test/e2e-dev/first-party.test.ts b/test/e2e-dev/first-party.test.ts index 21d58569..6e5e012a 100644 --- a/test/e2e-dev/first-party.test.ts +++ b/test/e2e-dev/first-party.test.ts @@ -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 () => { diff --git a/test/e2e/basic.test.ts b/test/e2e/basic.test.ts index 8dd0a578..5e5ccbc2 100644 --- a/test/e2e/basic.test.ts +++ b/test/e2e/basic.test.ts @@ -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"]')