Skip to content

v2.0.0-beta.14: trigger, consent, validation, and docs bugs #949

Description

@harlan-zw

🤖 An agent (Claude Code) filed this issue while writing the @nuxt/scripts package Skill with skilld's generate-package-skill. It reproduced each item against @nuxt/scripts 2.0.0-beta.14.

Environment: @nuxt/scripts 2.0.0-beta.14, Nuxt 4.6.0, unhead and @unhead/vue 3.4.2, Node.js 24.18.0, pnpm 12.4.2, headless Chromium. Each repro is a fresh Nuxt 4 app with modules: ['@nuxt/scripts']. Production results come from nuxt build and node .output/server/index.mjs.

Items are ordered by impact: silent wrong results, then type failures, then doc mismatches, then cleanups.

Silent wrong results

1. trigger: false on a registry entry blocks every composable call

Expected (per docs): the v0 to v1 guide says to set trigger: false when you load the script from a composable. It keeps the infrastructure and lets the composable load the script (1.v0-to-v1.md#L46). The build warning also offers trigger: false.

Observed: a composable call with no trigger never loads. Its status stays awaitingLoad, and the browser makes no request. The module copies trigger: false into runtime config (module.ts#L487), and useRegistryScript merges it into the call (utils.ts#L94).

// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@nuxt/scripts'],
  scripts: { registry: { metaPixel: { id: '123456', trigger: false } } },
})
<!-- app/pages/index.vue -->
<script setup lang="ts">
const { status } = useScriptMetaPixel()
</script>

<template>
  <p>{{ status }}</p>
</template>

Output after 6 seconds, in dev and production: awaitingLoad. { id: '123456' } with no trigger key loads.

Suggestion: drop an inherited trigger: false in composable calls, or document that trigger: false blocks them.

2. A later call without a trigger loads a consent-gated script

Expected (per docs): "Later composable calls return that script instance and do not need the options again" (1.registry-scripts.md#L158).

Observed: the later call returns the shared instance, but it also adds its own default onNuxtReady trigger. The script loads before consent.

// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@nuxt/scripts'],
  scripts: { registry: { metaPixel: { id: '123456' } } },
})
// app/utils/consent.ts
export const scriptsConsent = useScriptTriggerConsent()
<!-- app/pages/index.vue: first call, gated -->
<script setup lang="ts">
const { status } = useScriptMetaPixel({ scriptOptions: { trigger: scriptsConsent } })
</script>

<template>
  <PixelEvent />
  <p>{{ scriptsConsent.consented }} {{ status }}</p>
</template>
<!-- app/components/PixelEvent.vue: second call, no options -->
<script setup lang="ts">
const { proxy } = useScriptMetaPixel()
proxy.fbq('track', 'ViewContent')
</script>

<template>
  <span />
</template>

Output after 3 seconds, with no accept: consented is false and status is loaded.

Suggestion: make a later call reuse the first call's trigger, or say in the consent guide that every call needs the consent trigger.

3. Dev option validation never runs for bundled scripts

Expected (per docs): useScriptCloudflareWebAnalytics({ token: '123' }) is "skipped in development because the token is too short" (1.registry-scripts.md#L46).

Observed: in nuxt dev the script loads and sends beacons. The bundle transform rewrites the call to useScriptCloudflareWebAnalytics({ scriptInput: { src: '/_scripts/assets/78f1a00939270893.js' }, token: "123" }). Validation is skipped when scriptInput.src is set (utils.ts#L211).

<!-- app/pages/index.vue -->
<script setup lang="ts">
const { status } = useScriptCloudflareWebAnalytics({ token: '123' })
</script>

<template>
  <p>{{ status }}</p>
</template>

Output in nuxt dev: loaded. With scriptOptions: { bundle: false }, the browser console logs "Skipping script cloudflareWebAnalytics due to invalid options: Invalid length: Expected >=32 but received 3", and the status stays awaitingLoad.

Suggestion: validate before the bundle transform adds scriptInput.src, or skip validation only for a user-supplied src.

4. warmupStrategy: false still preloads with the client trigger

Expected (per docs): "false disables warmup entirely" (5.nuxt-config.md#L179).

Observed: Nuxt Scripts passes false through (useScript.ts#L321-L325), but SSR still writes a preload for a client trigger. onNuxtReady with false writes none.

<!-- app/pages/index.vue -->
<script setup lang="ts">
useScript('https://example.com/warm-client.js', { trigger: 'client', warmupStrategy: false })
useScript('https://example.com/warm-ready.js', { trigger: 'onNuxtReady', warmupStrategy: false })
</script>

<template>
  <p>warmup</p>
</template>

Output in the SSR HTML: <link href="https://example.com/warm-client.js" rel="preload" ... fetchpriority="low" as="script">, and no link for warm-ready.js.

Suggestion: honour false on the client path, or document the exception.

Type failures

5. The consent guide's consent.update() fails strict typecheck

Expected (per docs): the consent guide calls consent.update({ ... }) on the composable result (3.consent.md#L105).

Observed: consent is optional in UseScriptContext (types.ts#L134). At runtime it exists only on the client.

<!-- app/app.vue -->
<script setup lang="ts">
const { consent } = useScriptGoogleAnalytics({ defaultConsent: { analytics_storage: 'denied' } })
function accept() {
  consent.update({ analytics_storage: 'granted' })
}
</script>

<template>
  <button @click="accept">
    Accept
  </button>
</template>

Command: nuxt typecheck. Output: app/app.vue(4,3): error TS18048: 'consent' is possibly 'undefined'.

Suggestion: use consent?.update(...) in the guide, or type consent as present and make it a no-op on the server.

6. trigger: 'manual' in a globals tuple works but fails typecheck

Expected (per docs): the second tuple item takes useScript options (4.global.md). 'manual' is a useScript trigger, and the registry 'mock' entry uses it internally.

Observed: at runtime the global stays awaitingLoad, as expected. Typecheck rejects it, because NuxtUseScriptOptionsSerializable has no 'manual' (types.ts#L220).

// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@nuxt/scripts'],
  scripts: { globals: { manualGlobal: [{ src: '/manual-global.js' }, { trigger: 'manual' }] } },
})

Command: nuxt typecheck. Output: Type '"manual"' is not assignable to type '"server" | "client" | "onNuxtReady" | { idleTimeout: number; } | { interaction: string[]; } | { serviceWorker: true; } | undefined'.

Suggestion: add 'manual' to the serializable trigger type.

Doc mismatches

7. The @nuxt/scripts-devtools install line cannot install

Expected (per docs): pnpm add -D @nuxt/scripts-devtools@2.0.0-beta.12 (2.installation.md#L34). The v1 to v2 guide names the same package (2.v1-to-v2.md#L42).

Observed: npm view @nuxt/scripts-devtools versions returns npm error code E404. No version is published.

Suggestion: publish the package at the core version, or remove the install line until it exists.

8. The missing-trigger warning is silent for env-backed fields

Expected (per docs): "A build warning fires when you supply config values without a trigger" (1.v0-to-v1.md#L35).

Observed: the warning skips fields that have an environment variable (module.ts#L676-L680). The guide's own example gets no warning.

// nuxt.config.ts
export default defineNuxtConfig({
  modules: ['@nuxt/scripts'],
  scripts: { registry: { googleAnalytics: { id: 'G-XXXXXX' } } },
})

Command: nuxt prepare. Output: no warning. { id: 'G-XXXXXX', l: 'dl' } prints "registry.googleAnalytics: config provided without a trigger. The script will not auto-load."

Suggestion: warn for any user-supplied field, or reword the guide.

9. The install and migration commands fetch other versions

Expected (per docs): the v2 installation page and the v1 to v2 guide install and migrate to v2 (2.installation.md#L19, 2.v1-to-v2.md#L30-L31).

Observed: npm latest for @nuxt/scripts is 1.3.12, and npm latest for @nuxt/scripts-cli is 2.0.0-beta.1.

pnpm dlx nuxi@4.0.0 module add scripts
# ● Installing @nuxt/scripts@1.3.12 as a dependency
npm view @nuxt/scripts-cli dist-tags --json
# { "latest": "2.0.0-beta.1", "beta": "2.0.0-beta.14" }

pnpm dlx nuxi@4.0.0 module add @nuxt/scripts@2.0.0-beta.14 installs beta.14 and the unhead and @unhead/vue peers.

Suggestion: pin @beta or the exact version in the v2 docs while v2 is in beta, and move the CLI latest tag to the current beta.

Cleanups

10. The migration CLI misses renamed APIs on a renamed proxy

Expected (per docs): the CLI handles "mechanical API renames" (2.v1-to-v2.md#L34).

Observed: the rename regex matches only the literal name proxy (cli.ts#L273).

<!-- app/components/Track.vue -->
<script setup lang="ts">
const { proxy: rybbit } = useScriptRybbitAnalytics()
rybbit.rybbit.pageview()
</script>

Command: pnpm dlx @nuxt/scripts-cli@2.0.0-beta.14 migrate v2. Output: the file is unchanged, and no manual follow-up is listed. proxy.ttq('track', ...) in the same project was rewritten.

Suggestion: list unmatched .rybbit.pageview and ttq( calls as manual follow-ups.

11. The migration CLI drops the final newline

Observed: after pnpm dlx @nuxt/scripts-cli@2.0.0-beta.14 migrate v2, the rewritten nuxt.config.ts ends without a newline (\ No newline at end of file in diff). The CLI writes generateCode(module).code as is (cli.ts#L250).

Suggestion: keep the original final newline.

12. v2 ignores a leftover scripts.security block silently

Expected (per docs): the v1 to v2 guide does not mention security, but v2 removed URL signing and the option.

Observed: scripts: { security: { autoGenerateSecret: false } } gives no warning in nuxt prepare. The migration CLI keeps the block and lists no follow-up for it.

Suggestion: warn once when scripts.security is set, and list it in the guide and as a CLI follow-up.

Activity

  1. harlan-zw commented on Oct 8, 2026

    @harlan-zw
    CollaboratorAuthor

    🤖 ISSUE TRIAGE

    Harlan Agent Kit posted this automated triage. It is not Harlan's personal assessment or commitment. AI open source policy.

    • Route: Ready to spec
    • Difficulty: 4/5
    • Impact: 4/5
    • Reproduction: Yes
    • Codebase review: Needed
    • Summary: Verified 12 defects filed against v2.0.0-beta.14; every cited file and symbol exists at HEAD. Items 1-3 are silent wrong behavior: registry trigger: false is defu-merged into every composable call so scripts never load (module.ts:485-489,
    • Next action: Write a v2 beta hardening spec that groups the 12 items by package (script runtime, types, cli, docs) and decides fix-versus-document per item; define the trigger-resolution rules for registry-level trigger: false inheritance and later composable-call trigger reuse, order dev validation before the bundle transform, and split out the npm publishing actions (publish @nuxt/scripts-devtools, move @nuxt/scripts-cli latest to beta, pin @beta in v2 docs) as human release decisions. Hand the spec to one implementation Agent for the code items.
    • Related pull requests: fix: respect warmupStrategy: false when defaulting to preload #828: Merged fix that respects warmupStrategy: false when defaulting to preload; issue item 4 shows the SSR preload for the client trigger path still fires, so the same warmup behavior needs its remaining half fixed in this spec.
  2. added
    harlan-agent-ready-to-specIssue triage found work that needs a specification before implementation.
    harlan-agent-runningAn Agent holds a Task on this issue or pull request right now.
    and removed
    harlan-agent-runningAn Agent holds a Task on this issue or pull request right now.
    on Oct 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    harlan-agent-ready-to-specIssue triage found work that needs a specification before implementation.

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions