From c2ab741c0341aa0e56659d4d52c7927e74e18b73 Mon Sep 17 00:00:00 2001 From: Harlan Wilton Date: Sun, 29 Mar 2026 14:15:35 +1100 Subject: [PATCH 1/5] fix: add v0 migration warnings for deprecated config keys Detect and auto-migrate `reverseProxyIntercept` to `proxy` in registry config with a build warning. Document the rename and Google Maps component consolidation in the v0-to-v1 migration guide. --- .../docs/4.migration-guide/1.v0-to-v1.md | 1 + packages/script/src/module.ts | 3 +- packages/script/src/normalize.ts | 43 +++++++++++++++ test/unit/normalize.test.ts | 54 ++++++++++++++++++- 4 files changed, 98 insertions(+), 3 deletions(-) diff --git a/docs/content/docs/4.migration-guide/1.v0-to-v1.md b/docs/content/docs/4.migration-guide/1.v0-to-v1.md index 9fcbda7f0..d5483ac6a 100644 --- a/docs/content/docs/4.migration-guide/1.v0-to-v1.md +++ b/docs/content/docs/4.migration-guide/1.v0-to-v1.md @@ -200,6 +200,7 @@ v1 redesigns how registry entries work. The key change: **presence enables infra | `[{ id: '...' }, { bundle: true }]` | `{ id: '...' }` | Bundling is auto-enabled via capabilities; no need to opt in | | `[{ id: '...' }, { trigger: 'onNuxtReady' }]` | `{ id: '...', trigger: 'onNuxtReady' }` | Array tuple syntax still works, but flat config is preferred | | `googleAnalytics: 'mock'` | `googleAnalytics: 'mock'` | Unchanged; creates a stub for testing | +| `{ reverseProxyIntercept: false }` | `{ proxy: false }` | Renamed; auto-migrated with a build warning | ### Flat Config Syntax diff --git a/packages/script/src/module.ts b/packages/script/src/module.ts index eef3e6896..f6d31dba0 100644 --- a/packages/script/src/module.ts +++ b/packages/script/src/module.ts @@ -32,7 +32,7 @@ import { setupPublicAssetStrategy } from './assets' import { buildDevtoolsData, buildDevtoolsEntry, setupDevtools } from './devtools' import { installNuxtModule } from './kit' import { logger } from './logger' -import { extractRequiredFields, normalizeRegistryConfig } from './normalize' +import { extractRequiredFields, migrateDeprecatedRegistryKeys, normalizeRegistryConfig } from './normalize' import { NuxtScriptsCheckScripts } from './plugins/check-scripts' import { generateInterceptPluginContents } from './plugins/intercept' import { NuxtScriptBundleTransformer } from './plugins/transform' @@ -332,6 +332,7 @@ export default defineNuxtModule({ // Normalize registry entries to [input, scriptOptions?] tuple form // Eliminates 4-shape polymorphism (true | 'mock' | object | array) for all downstream consumers if (config.registry) { + migrateDeprecatedRegistryKeys(config.registry as Record, msg => logger.warn(msg)) normalizeRegistryConfig(config.registry as Record) nuxt.options.runtimeConfig.public = nuxt.options.runtimeConfig.public || {} diff --git a/packages/script/src/normalize.ts b/packages/script/src/normalize.ts index c7ea5deec..c06e113bf 100644 --- a/packages/script/src/normalize.ts +++ b/packages/script/src/normalize.ts @@ -23,6 +23,49 @@ export function extractRequiredFields(schema: RegistryScript['schema']): string[ .map(([key]) => key) } +/** + * Rewrite deprecated config keys in-place before normalization. + * Currently handles: `reverseProxyIntercept` → `proxy`. + */ +export function migrateDeprecatedRegistryKeys( + registry: Record, + warn: (msg: string) => void, +): void { + for (const key of Object.keys(registry)) { + const entry = registry[key] + if (!entry || typeof entry !== 'object') + continue + + if (Array.isArray(entry)) { + // Array tuple: check scriptOptions (second element) + const opts = entry[1] + if (opts && typeof opts === 'object' && 'reverseProxyIntercept' in opts) { + warn(`registry.${key}: \`reverseProxyIntercept\` has been renamed to \`proxy\`. Please update your config. Auto-migrating for now.`) + const o = opts as Record + o.proxy ??= o.reverseProxyIntercept + delete o.reverseProxyIntercept + } + } + else { + const obj = entry as Record + // Top-level key + if ('reverseProxyIntercept' in obj) { + warn(`registry.${key}: \`reverseProxyIntercept\` has been renamed to \`proxy\`. Please update your config. Auto-migrating for now.`) + obj.proxy ??= obj.reverseProxyIntercept + delete obj.reverseProxyIntercept + } + // Nested scriptOptions + const so = obj.scriptOptions + if (so && typeof so === 'object' && 'reverseProxyIntercept' in so) { + warn(`registry.${key}: \`scriptOptions.reverseProxyIntercept\` has been renamed to \`proxy\`. Please update your config. Auto-migrating for now.`) + const s = so as Record + s.proxy ??= s.reverseProxyIntercept + delete s.reverseProxyIntercept + } + } + } +} + /** * Normalize all registry config entries in-place to [input, scriptOptions?] tuple form. * diff --git a/test/unit/normalize.test.ts b/test/unit/normalize.test.ts index ababacdac..86d7a0d88 100644 --- a/test/unit/normalize.test.ts +++ b/test/unit/normalize.test.ts @@ -1,5 +1,5 @@ -import { describe, expect, it } from 'vitest' -import { normalizeRegistryConfig } from '../../packages/script/src/normalize' +import { describe, expect, it, vi } from 'vitest' +import { migrateDeprecatedRegistryKeys, normalizeRegistryConfig } from '../../packages/script/src/normalize' describe('normalizeRegistryConfig', () => { it('normalizes true to [{}, { trigger: "onNuxtReady" }]', () => { @@ -108,3 +108,53 @@ describe('normalizeRegistryConfig', () => { expect(registry.stripe).toBeUndefined() }) }) + +describe('migrateDeprecatedRegistryKeys', () => { + it('rewrites reverseProxyIntercept in flat object to proxy', () => { + const warn = vi.fn() + const registry: Record = { ga: { id: 'G-xxx', reverseProxyIntercept: false } } + migrateDeprecatedRegistryKeys(registry, warn) + expect(registry.ga).toEqual({ id: 'G-xxx', proxy: false }) + expect(warn).toHaveBeenCalledOnce() + expect(warn.mock.calls[0][0]).toContain('reverseProxyIntercept') + }) + + it('rewrites reverseProxyIntercept in nested scriptOptions', () => { + const warn = vi.fn() + const registry: Record = { ga: { id: 'G-xxx', scriptOptions: { reverseProxyIntercept: false } } } + migrateDeprecatedRegistryKeys(registry, warn) + expect(registry.ga.scriptOptions).toEqual({ proxy: false }) + expect(warn).toHaveBeenCalledOnce() + }) + + it('rewrites reverseProxyIntercept in array tuple scriptOptions', () => { + const warn = vi.fn() + const registry: Record = { ga: [{ id: 'G-xxx' }, { reverseProxyIntercept: false }] } + migrateDeprecatedRegistryKeys(registry, warn) + expect(registry.ga[1]).toEqual({ proxy: false }) + expect(warn).toHaveBeenCalledOnce() + }) + + it('does not clobber existing proxy when both are present', () => { + const warn = vi.fn() + const registry: Record = { ga: { id: 'G-xxx', proxy: true, reverseProxyIntercept: false } } + migrateDeprecatedRegistryKeys(registry, warn) + expect(registry.ga).toEqual({ id: 'G-xxx', proxy: true }) + expect(warn).toHaveBeenCalledOnce() + }) + + it('does not warn when reverseProxyIntercept is absent', () => { + const warn = vi.fn() + const registry: Record = { ga: { id: 'G-xxx', proxy: false } } + migrateDeprecatedRegistryKeys(registry, warn) + expect(registry.ga).toEqual({ id: 'G-xxx', proxy: false }) + expect(warn).not.toHaveBeenCalled() + }) + + it('skips non-object entries', () => { + const warn = vi.fn() + const registry: Record = { ga: true, posthog: 'mock', stripe: false } + migrateDeprecatedRegistryKeys(registry, warn) + expect(warn).not.toHaveBeenCalled() + }) +}) From b3675d0756fb7cf7dd060635eadce04d156930a4 Mon Sep 17 00:00:00 2001 From: Harlan Wilton Date: Sun, 29 Mar 2026 14:27:40 +1100 Subject: [PATCH 2/5] fix: warn when registry config has input but no trigger In v0, all configured scripts auto-loaded. In v1, a trigger is required for auto-loading. Emit a build warning when input fields are provided without a trigger so v0 users know their scripts stopped loading. Also expand the migration guide with a dedicated section explaining this behavior change with before/after examples. --- .../docs/4.migration-guide/1.v0-to-v1.md | 38 ++++++++++++++++++- packages/script/src/module.ts | 12 +++++- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/docs/content/docs/4.migration-guide/1.v0-to-v1.md b/docs/content/docs/4.migration-guide/1.v0-to-v1.md index d5483ac6a..0e0b491eb 100644 --- a/docs/content/docs/4.migration-guide/1.v0-to-v1.md +++ b/docs/content/docs/4.migration-guide/1.v0-to-v1.md @@ -192,15 +192,49 @@ Server-side geocoding proxy to reduce billing costs and hide API keys. Automatic v1 redesigns how registry entries work. The key change: **presence enables infrastructure, `trigger` enables auto-loading**. +### Scripts No Longer Auto-Load Without a Trigger + +In v0, any configured script auto-loaded globally. In v1, scripts require an explicit `trigger` to auto-load. Without one, the script registers infrastructure (proxy routes, types, bundling) but does **not** load on the page. + +If you had a config like this in v0: + +```ts [v0 nuxt.config.ts] +scripts: { + registry: { + googleAnalytics: { id: 'G-XXXXXX' }, + } +} +``` + +You need to add a trigger in v1: + +```ts [v1 nuxt.config.ts] +scripts: { + registry: { + googleAnalytics: { id: 'G-XXXXXX', trigger: 'onNuxtReady' }, + } +} +``` + +Or use the `true` shorthand to auto-load with default settings: + +```ts +googleAnalytics: true // auto-loads with trigger: 'onNuxtReady' +``` + +::callout{icon="i-heroicons-exclamation-triangle" color="amber"} +A build warning will appear if you provide config values without a `trigger`. This is intentional: in v1 you must opt in to auto-loading. +:: + ### Config Migration | v0 | v1 | What changed | |----|-----|--------------| +| `googleAnalytics: { id: '...' }` | `googleAnalytics: { id: '...', trigger: 'onNuxtReady' }` | Scripts no longer auto-load without an explicit `trigger` | | `googleAnalytics: true` | `googleAnalytics: true` | Still works; alias for `{ trigger: 'onNuxtReady' }` (auto-load). Use `{}` for proxy/bundling infrastructure without auto-loading | -| `[{ id: '...' }, { bundle: true }]` | `{ id: '...' }` | Bundling is auto-enabled via capabilities; no need to opt in | +| `[{ id: '...' }, { bundle: true }]` | `{ id: '...', trigger: 'onNuxtReady' }` | Bundling is auto-enabled via capabilities; add `trigger` to auto-load | | `[{ id: '...' }, { trigger: 'onNuxtReady' }]` | `{ id: '...', trigger: 'onNuxtReady' }` | Array tuple syntax still works, but flat config is preferred | | `googleAnalytics: 'mock'` | `googleAnalytics: 'mock'` | Unchanged; creates a stub for testing | -| `{ reverseProxyIntercept: false }` | `{ proxy: false }` | Renamed; auto-migrated with a build warning | ### Flat Config Syntax diff --git a/packages/script/src/module.ts b/packages/script/src/module.ts index f6d31dba0..a8e150428 100644 --- a/packages/script/src/module.ts +++ b/packages/script/src/module.ts @@ -32,7 +32,7 @@ import { setupPublicAssetStrategy } from './assets' import { buildDevtoolsData, buildDevtoolsEntry, setupDevtools } from './devtools' import { installNuxtModule } from './kit' import { logger } from './logger' -import { extractRequiredFields, migrateDeprecatedRegistryKeys, normalizeRegistryConfig } from './normalize' +import { extractRequiredFields, normalizeRegistryConfig } from './normalize' import { NuxtScriptsCheckScripts } from './plugins/check-scripts' import { generateInterceptPluginContents } from './plugins/intercept' import { NuxtScriptBundleTransformer } from './plugins/transform' @@ -332,7 +332,6 @@ export default defineNuxtModule({ // Normalize registry entries to [input, scriptOptions?] tuple form // Eliminates 4-shape polymorphism (true | 'mock' | object | array) for all downstream consumers if (config.registry) { - migrateDeprecatedRegistryKeys(config.registry as Record, msg => logger.warn(msg)) normalizeRegistryConfig(config.registry as Record) nuxt.options.runtimeConfig.public = nuxt.options.runtimeConfig.public || {} @@ -460,6 +459,15 @@ export default defineNuxtModule({ if (missing.length) { logger.warn(`[nuxt-scripts] registry.${key}: missing required field${missing.length > 1 ? 's' : ''} ${missing.map(f => `'${f}'`).join(', ')}. The script infrastructure is registered but will not function without ${missing.length > 1 ? 'them' : 'it'}.`) } + // Warn when a user provides input config but no trigger: the script won't auto-load. + // In v0 all configured scripts auto-loaded; in v1 a trigger is required. + if (Object.keys(input).length > 0 && !scriptOptions?.trigger) { + logger.warn( + `[nuxt-scripts] registry.${key}: config provided without a \`trigger\`. ` + + `The script will not auto-load. Add \`trigger: 'onNuxtReady'\` to auto-load, or use \`true\` as a shorthand. ` + + `See https://scripts.nuxt.com/docs/migration-guide/v0-to-v1`, + ) + } } } From 4b1f1ff54751e43198e271771e7d1bc811f95a3f Mon Sep 17 00:00:00 2001 From: Harlan Wilton Date: Sun, 29 Mar 2026 14:29:49 +1100 Subject: [PATCH 3/5] fix: wrap migration guide code blocks in defineNuxtConfig --- .../docs/4.migration-guide/1.v0-to-v1.md | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/docs/content/docs/4.migration-guide/1.v0-to-v1.md b/docs/content/docs/4.migration-guide/1.v0-to-v1.md index 0e0b491eb..c56e02ced 100644 --- a/docs/content/docs/4.migration-guide/1.v0-to-v1.md +++ b/docs/content/docs/4.migration-guide/1.v0-to-v1.md @@ -199,27 +199,37 @@ In v0, any configured script auto-loaded globally. In v1, scripts require an exp If you had a config like this in v0: ```ts [v0 nuxt.config.ts] -scripts: { - registry: { - googleAnalytics: { id: 'G-XXXXXX' }, +export default defineNuxtConfig({ + scripts: { + registry: { + googleAnalytics: { id: 'G-XXXXXX' }, + } } -} +}) ``` You need to add a trigger in v1: ```ts [v1 nuxt.config.ts] -scripts: { - registry: { - googleAnalytics: { id: 'G-XXXXXX', trigger: 'onNuxtReady' }, +export default defineNuxtConfig({ + scripts: { + registry: { + googleAnalytics: { id: 'G-XXXXXX', trigger: 'onNuxtReady' }, + } } -} +}) ``` Or use the `true` shorthand to auto-load with default settings: -```ts -googleAnalytics: true // auto-loads with trigger: 'onNuxtReady' +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + scripts: { + registry: { + googleAnalytics: true, // auto-loads with trigger: 'onNuxtReady' + } + } +}) ``` ::callout{icon="i-heroicons-exclamation-triangle" color="amber"} From 86413bbfcb59f795ada4f92b27416723254e6ea8 Mon Sep 17 00:00:00 2001 From: Harlan Wilton Date: Sun, 29 Mar 2026 14:50:28 +1100 Subject: [PATCH 4/5] fix: deprecate `true` shorthand, require explicit trigger, support `trigger: false` - `true` as a registry value now emits a deprecation warning; use `{ trigger: 'onNuxtReady' }` instead - `trigger: false` is a valid explicit opt-out (infrastructure only) - Missing trigger warning now checks key presence, not truthiness - Update all docs to use explicit `{ trigger: 'onNuxtReady' }` instead of `true` - Update scripts.nuxt.com code gen to emit trigger in all generated configs --- docs/content/docs/1.guides/1.registry-scripts.md | 5 ++--- docs/content/docs/4.migration-guide/1.v0-to-v1.md | 14 +++++++------- docs/content/scripts/crisp.md | 2 +- docs/content/scripts/google-maps/index.md | 2 +- docs/content/scripts/intercom.md | 2 +- docs/content/scripts/paypal.md | 2 +- docs/content/scripts/stripe.md | 4 ++-- docs/content/scripts/vercel-analytics.md | 2 +- packages/script/src/module.ts | 9 +++++---- packages/script/src/normalize.ts | 10 +++++++--- packages/script/src/runtime/types.ts | 2 +- test/unit/normalize.test.ts | 15 +++++++++++++++ 12 files changed, 44 insertions(+), 25 deletions(-) diff --git a/docs/content/docs/1.guides/1.registry-scripts.md b/docs/content/docs/1.guides/1.registry-scripts.md index ade24a9fe..ebc9d5b60 100644 --- a/docs/content/docs/1.guides/1.registry-scripts.md +++ b/docs/content/docs/1.guides/1.registry-scripts.md @@ -68,8 +68,7 @@ NUXT_PUBLIC_SCRIPTS_CLOUDFLARE_WEB_ANALYTICS_TOKEN=YOUR_TOKEN export default defineNuxtConfig({ scripts: { registry: { - // loads the script - cloudflareWebAnalytics: true, + cloudflareWebAnalytics: { trigger: 'onNuxtReady' }, }, }, runtimeConfig: { @@ -100,7 +99,7 @@ You can do this by providing a `mock` value to the registry script. export default defineNuxtConfig({ scripts: { registry: { - googleTagManager: true, + googleTagManager: { trigger: 'onNuxtReady' }, }, }, $development: { diff --git a/docs/content/docs/4.migration-guide/1.v0-to-v1.md b/docs/content/docs/4.migration-guide/1.v0-to-v1.md index c56e02ced..4de300caf 100644 --- a/docs/content/docs/4.migration-guide/1.v0-to-v1.md +++ b/docs/content/docs/4.migration-guide/1.v0-to-v1.md @@ -63,9 +63,9 @@ Enable the embeds you need in your `nuxt.config`: export default defineNuxtConfig({ scripts: { registry: { - xEmbed: true, - instagramEmbed: true, - blueskyEmbed: true, + xEmbed: { trigger: 'onNuxtReady' }, + instagramEmbed: { trigger: 'onNuxtReady' }, + blueskyEmbed: { trigger: 'onNuxtReady' }, }, }, }) @@ -220,20 +220,20 @@ export default defineNuxtConfig({ }) ``` -Or use the `true` shorthand to auto-load with default settings: +If you only need infrastructure (proxy routes, types, bundling) without loading the script, set `trigger: false` explicitly: ```ts [nuxt.config.ts] export default defineNuxtConfig({ scripts: { registry: { - googleAnalytics: true, // auto-loads with trigger: 'onNuxtReady' + googleAnalytics: { id: 'G-XXXXXX', trigger: false }, } } }) ``` ::callout{icon="i-heroicons-exclamation-triangle" color="amber"} -A build warning will appear if you provide config values without a `trigger`. This is intentional: in v1 you must opt in to auto-loading. +A build warning will appear if you provide config values without a `trigger`. Set `trigger: 'onNuxtReady'` to auto-load, or `trigger: false` for infrastructure only. :: ### Config Migration @@ -241,7 +241,7 @@ A build warning will appear if you provide config values without a `trigger`. Th | v0 | v1 | What changed | |----|-----|--------------| | `googleAnalytics: { id: '...' }` | `googleAnalytics: { id: '...', trigger: 'onNuxtReady' }` | Scripts no longer auto-load without an explicit `trigger` | -| `googleAnalytics: true` | `googleAnalytics: true` | Still works; alias for `{ trigger: 'onNuxtReady' }` (auto-load). Use `{}` for proxy/bundling infrastructure without auto-loading | +| `googleAnalytics: true` | `googleAnalytics: { trigger: 'onNuxtReady' }` | `true` shorthand is deprecated; use an explicit object with `trigger` | | `[{ id: '...' }, { bundle: true }]` | `{ id: '...', trigger: 'onNuxtReady' }` | Bundling is auto-enabled via capabilities; add `trigger` to auto-load | | `[{ id: '...' }, { trigger: 'onNuxtReady' }]` | `{ id: '...', trigger: 'onNuxtReady' }` | Array tuple syntax still works, but flat config is preferred | | `googleAnalytics: 'mock'` | `googleAnalytics: 'mock'` | Unchanged; creates a stub for testing | diff --git a/docs/content/scripts/crisp.md b/docs/content/scripts/crisp.md index 619eb5f32..524f2dbd6 100644 --- a/docs/content/scripts/crisp.md +++ b/docs/content/scripts/crisp.md @@ -107,7 +107,7 @@ If you prefer to configure your id using environment variables. export default defineNuxtConfig({ scripts: { registry: { - crisp: true, + crisp: { trigger: 'onNuxtReady' }, } }, // you need to provide a runtime config to access the environment variables diff --git a/docs/content/scripts/google-maps/index.md b/docs/content/scripts/google-maps/index.md index 6aca80caa..8fc18bae9 100644 --- a/docs/content/scripts/google-maps/index.md +++ b/docs/content/scripts/google-maps/index.md @@ -36,7 +36,7 @@ Enable Google Maps in your `nuxt.config` and provide your API key via environmen export default defineNuxtConfig({ scripts: { registry: { - googleMaps: true, + googleMaps: { trigger: 'onNuxtReady' }, }, }, runtimeConfig: { diff --git a/docs/content/scripts/intercom.md b/docs/content/scripts/intercom.md index 7e63a4a0d..f1232c3e6 100644 --- a/docs/content/scripts/intercom.md +++ b/docs/content/scripts/intercom.md @@ -87,7 +87,7 @@ If you prefer to configure your app ID using environment variables. export default defineNuxtConfig({ scripts: { registry: { - intercom: true, + intercom: { trigger: 'onNuxtReady' }, } }, // you need to provide a runtime config to access the environment variables diff --git a/docs/content/scripts/paypal.md b/docs/content/scripts/paypal.md index 888a49404..c9fa729b6 100644 --- a/docs/content/scripts/paypal.md +++ b/docs/content/scripts/paypal.md @@ -106,7 +106,7 @@ If you prefer to configure your client ID using environment variables. export default defineNuxtConfig({ scripts: { registry: { - paypal: true, + paypal: { trigger: 'onNuxtReady' }, } }, // you need to provide a runtime config to access the environment variables diff --git a/docs/content/scripts/stripe.md b/docs/content/scripts/stripe.md index 8fbb6c7a1..1fc4bdbb0 100644 --- a/docs/content/scripts/stripe.md +++ b/docs/content/scripts/stripe.md @@ -43,7 +43,7 @@ Stripe recommends loading their script globally on your app to improve fraud det export default defineNuxtConfig({ scripts: { registry: { - stripe: true, + stripe: { trigger: 'onNuxtReady' }, } } }) @@ -54,7 +54,7 @@ export default defineNuxtConfig({ $production: { scripts: { registry: { - stripe: true, + stripe: { trigger: 'onNuxtReady' }, } } } diff --git a/docs/content/scripts/vercel-analytics.md b/docs/content/scripts/vercel-analytics.md index 411a98d38..2498d4083 100644 --- a/docs/content/scripts/vercel-analytics.md +++ b/docs/content/scripts/vercel-analytics.md @@ -34,7 +34,7 @@ First-party mode is auto-enabled for Vercel Analytics. Nuxt bundles the analytic export default defineNuxtConfig({ scripts: { registry: { - vercelAnalytics: true, + vercelAnalytics: { trigger: 'onNuxtReady' }, } } }) diff --git a/packages/script/src/module.ts b/packages/script/src/module.ts index a8e150428..b2832a8ac 100644 --- a/packages/script/src/module.ts +++ b/packages/script/src/module.ts @@ -332,7 +332,7 @@ export default defineNuxtModule({ // Normalize registry entries to [input, scriptOptions?] tuple form // Eliminates 4-shape polymorphism (true | 'mock' | object | array) for all downstream consumers if (config.registry) { - normalizeRegistryConfig(config.registry as Record) + normalizeRegistryConfig(config.registry as Record, msg => logger.warn(msg)) nuxt.options.runtimeConfig.public = nuxt.options.runtimeConfig.public || {} // Auto-populate env var defaults for enabled registry scripts so that @@ -459,12 +459,13 @@ export default defineNuxtModule({ if (missing.length) { logger.warn(`[nuxt-scripts] registry.${key}: missing required field${missing.length > 1 ? 's' : ''} ${missing.map(f => `'${f}'`).join(', ')}. The script infrastructure is registered but will not function without ${missing.length > 1 ? 'them' : 'it'}.`) } - // Warn when a user provides input config but no trigger: the script won't auto-load. + // Warn when a user provides input config but no explicit trigger. // In v0 all configured scripts auto-loaded; in v1 a trigger is required. - if (Object.keys(input).length > 0 && !scriptOptions?.trigger) { + // trigger: false is valid (explicit infrastructure-only). + if (Object.keys(input).length > 0 && (!scriptOptions || !('trigger' in scriptOptions))) { logger.warn( `[nuxt-scripts] registry.${key}: config provided without a \`trigger\`. ` - + `The script will not auto-load. Add \`trigger: 'onNuxtReady'\` to auto-load, or use \`true\` as a shorthand. ` + + `The script will not auto-load. Add \`trigger: 'onNuxtReady'\` to auto-load, or \`trigger: false\` for infrastructure only. ` + `See https://scripts.nuxt.com/docs/migration-guide/v0-to-v1`, ) } diff --git a/packages/script/src/normalize.ts b/packages/script/src/normalize.ts index c06e113bf..1152440d7 100644 --- a/packages/script/src/normalize.ts +++ b/packages/script/src/normalize.ts @@ -4,7 +4,7 @@ import type { NuxtUseScriptOptionsSerializable, RegistryScript } from './runtime export type NormalizedRegistryEntry = [input: Record, scriptOptions?: NormalizedScriptOptions] export type NormalizedScriptOptions = Partial> & { - trigger?: NuxtUseScriptOptionsSerializable['trigger'] | 'manual' + trigger?: NuxtUseScriptOptionsSerializable['trigger'] | 'manual' | false skipValidation?: boolean } @@ -78,10 +78,13 @@ export function migrateDeprecatedRegistryKeys( * - `[input, scriptOptions]` → unchanged (internal/backwards compat) * * Aliases: - * - `true` → `[{}, { trigger: 'onNuxtReady' }]` (auto-load globally) + * - `true` → `[{}, { trigger: 'onNuxtReady' }]` (deprecated, use `{ trigger: 'onNuxtReady' }`) * - `'proxy-only'` → build error with migration message */ -export function normalizeRegistryConfig(registry: Record): void { +export function normalizeRegistryConfig( + registry: Record, + warn?: (msg: string) => void, +): void { for (const key of Object.keys(registry)) { const entry = registry[key] if (!entry) { @@ -89,6 +92,7 @@ export function normalizeRegistryConfig(registry: Record): void continue } if (entry === true) { + warn?.(`registry.${key}: \`true\` shorthand is deprecated. Use \`{ trigger: 'onNuxtReady' }\` instead.`) registry[key] = [{}, { trigger: 'onNuxtReady' }] satisfies NormalizedRegistryEntry continue } diff --git a/packages/script/src/runtime/types.ts b/packages/script/src/runtime/types.ts index 1b8accf7f..344ef39bb 100644 --- a/packages/script/src/runtime/types.ts +++ b/packages/script/src/runtime/types.ts @@ -251,7 +251,7 @@ export type RegistryScriptKey = Exclude type RegistryConfigInput = [T] extends [true] ? Record : T -export type NuxtConfigScriptRegistryEntry = true | false | 'mock' | (RegistryConfigInput & { trigger?: NuxtUseScriptOptionsSerializable['trigger'], proxy?: boolean, bundle?: boolean, partytown?: boolean, privacy?: ProxyPrivacyInput }) +export type NuxtConfigScriptRegistryEntry = true | false | 'mock' | (RegistryConfigInput & { trigger?: NuxtUseScriptOptionsSerializable['trigger'] | false, proxy?: boolean, bundle?: boolean, partytown?: boolean, privacy?: ProxyPrivacyInput }) export type NuxtConfigScriptRegistry = Partial<{ [key in T]: NuxtConfigScriptRegistryEntry }> & Record> diff --git a/test/unit/normalize.test.ts b/test/unit/normalize.test.ts index 86d7a0d88..5e3ac945e 100644 --- a/test/unit/normalize.test.ts +++ b/test/unit/normalize.test.ts @@ -8,6 +8,15 @@ describe('normalizeRegistryConfig', () => { expect(registry.plausible).toEqual([{}, { trigger: 'onNuxtReady' }]) }) + it('warns when true shorthand is used', () => { + const warn = vi.fn() + const registry: Record = { plausible: true } + normalizeRegistryConfig(registry, warn) + expect(warn).toHaveBeenCalledOnce() + expect(warn.mock.calls[0][0]).toContain('true') + expect(warn.mock.calls[0][0]).toContain('deprecated') + }) + it('throws on "proxy-only" with migration message', () => { const registry: Record = { ga: 'proxy-only' } expect(() => normalizeRegistryConfig(registry)).toThrowError(/proxy-only.*no longer supported/) @@ -37,6 +46,12 @@ describe('normalizeRegistryConfig', () => { expect(registry.ga).toEqual([{ id: 'G-xxx' }, { trigger: 'onNuxtReady' }]) }) + it('hoists trigger: false to scriptOptions', () => { + const registry: Record = { ga: { id: 'G-xxx', trigger: false } } + normalizeRegistryConfig(registry) + expect(registry.ga).toEqual([{ id: 'G-xxx' }, { trigger: false }]) + }) + it('hoists proxy to scriptOptions', () => { const registry: Record = { plausible: { domain: 'mysite.com', proxy: false } } normalizeRegistryConfig(registry) From e7ab836cf93f4e1019afc060a8a37d5f96aa6c61 Mon Sep 17 00:00:00 2001 From: Harlan Wilton Date: Sun, 29 Mar 2026 15:48:35 +1100 Subject: [PATCH 5/5] fix: call migrateDeprecatedRegistryKeys, consistent prefixes, reduce false positives - Actually call migrateDeprecatedRegistryKeys() before normalization (was dead code) - Add [nuxt-scripts] prefix to all warnings in normalize.ts for consistency - Filter env-var-only defaults from trigger warning to avoid false positives - Clarify trigger: false in migration guide (proxy routes, types, bundling only) - Condense config migration table (details already covered in prose above) --- docs/content/docs/4.migration-guide/1.v0-to-v1.md | 9 +++------ packages/script/src/module.ts | 8 ++++++-- packages/script/src/normalize.ts | 8 ++++---- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/docs/content/docs/4.migration-guide/1.v0-to-v1.md b/docs/content/docs/4.migration-guide/1.v0-to-v1.md index 4de300caf..69f7c8e6a 100644 --- a/docs/content/docs/4.migration-guide/1.v0-to-v1.md +++ b/docs/content/docs/4.migration-guide/1.v0-to-v1.md @@ -220,7 +220,7 @@ export default defineNuxtConfig({ }) ``` -If you only need infrastructure (proxy routes, types, bundling) without loading the script, set `trigger: false` explicitly: +If you only need infrastructure without loading the script on the page, set `trigger: false` explicitly. This registers proxy routes, TypeScript types, and bundling config, but no `