diff --git a/README.md b/README.md index b18f307c..638df4e6 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,13 @@ [![License][license-src]][license-href] [![Nuxt][nuxt-src]][nuxt-href] [![Volta][volta-src]][volta-href] + + + + + Skill repository on skilld.dev + + # Nuxt Scripts @@ -38,10 +45,7 @@ npx nuxi@latest module add scripts ``` > [!TIP] -> Generate an Agent Skill for this package using [skilld](https://github.com/harlan-zw/skilld): -> ```bash -> npx skilld add @nuxt/scripts -> ``` +> Using an AI agent? Get the @nuxt/scripts Skill on [skilld.dev/gh/nuxt/scripts/nuxt-scripts](https://skilld.dev/gh/nuxt/scripts/nuxt-scripts). That's it. The Nuxt Scripts module should be downloaded and added to your Nuxt Config `modules`. diff --git a/docs/content/docs/1.getting-started/2.installation.md b/docs/content/docs/1.getting-started/2.installation.md index dbd67223..405627d6 100644 --- a/docs/content/docs/1.getting-started/2.installation.md +++ b/docs/content/docs/1.getting-started/2.installation.md @@ -20,10 +20,7 @@ npx nuxi@latest module add scripts ``` > [!TIP] -> Generate an Agent Skill for this package using [skilld](https://github.com/skilld-dev/skilld): -> ```bash -> npx skilld add @nuxt/scripts -> ``` +> Using an AI agent? Get the @nuxt/scripts Skill on [skilld.dev/gh/nuxt/scripts/nuxt-scripts](https://skilld.dev/gh/nuxt/scripts/nuxt-scripts). The [Nuxt module command](https://nuxt.com/docs/api/commands/module) installs Nuxt Scripts and adds it to the `modules` array in your Nuxt config. diff --git a/packages/script/package.json b/packages/script/package.json index 5cc097b7..ee80f3eb 100644 --- a/packages/script/package.json +++ b/packages/script/package.json @@ -47,7 +47,8 @@ }, "files": [ "THIRD_PARTY_LICENSES.md", - "dist" + "dist", + "skills" ], "engines": { "node": "^22.22.3 || ^24.15.0 || >=26.0.0" diff --git a/packages/script/skills/nuxt-scripts/SKILL.md b/packages/script/skills/nuxt-scripts/SKILL.md new file mode 100644 index 00000000..f30ff87e --- /dev/null +++ b/packages/script/skills/nuxt-scripts/SKILL.md @@ -0,0 +1,192 @@ +--- +name: nuxt-scripts +description: Loads and controls third-party scripts in Nuxt with @nuxt/scripts v2, covering the scripts.registry config key, useScript, registry composables such as useScriptGoogleAnalytics and useScriptMetaPixel, consent triggers, bundling, and the first-party proxy. Use when adding analytics, pixels, or chat scripts to Nuxt, upgrading @nuxt/scripts from v1 to v2, or when a script never loads, loads before consent, or fails the build while downloading. +--- + +# @nuxt/scripts + +Tested against `@nuxt/scripts` 2.0.0-beta.14 (npm `beta`) with Nuxt 4.6.0. +v2 needs Nuxt `^4.6.0 || ^5.0.0`, `@unhead/vue` and `unhead` `^3.4.2`, and Node.js `^22.22.3 || ^24.15.0 || >=26.0.0`. +Composables and components are auto-imported. The examples import from `#imports` only to stay complete modules. Paths assume the Nuxt 4 `app/` source directory. + +## Setup + +Install with `npx nuxi module add @nuxt/scripts@2.0.0-beta.14`. It also adds the `unhead` and `@unhead/vue` peers. Keep the version: without it, nuxi installs the npm `latest` tag, which can be another major. + +A registry entry is a flat object, `'mock'`, or `false`; any other shape throws. Its `trigger` decides whether it loads globally: + +| `scripts.registry` entry | Global script | Composable called with no options | +|---|---|---| +| `{ id, trigger: 'onNuxtReady' }` | loads on every page | returns the global instance | +| `{ id }` or `{}` | none | loads with the default `onNuxtReady` trigger | +| `{ id, trigger: false }` | none | never loads: it inherits `trigger: false` | +| `'mock'` | none | stays `awaitingLoad`; proxy calls do nothing | + +```ts +// nuxt.config.ts +import { defineNuxtConfig } from 'nuxt/config' + +export default defineNuxtConfig({ + modules: ['@nuxt/scripts'], + scripts: { + registry: { + // Cookieless, so it loads on every page. The script ID comes from + // NUXT_PUBLIC_SCRIPTS_PLAUSIBLE_ANALYTICS_SCRIPT_ID and must be real + // at build time (see Deploy). + plausibleAnalytics: { trigger: 'onNuxtReady' }, + // No trigger: app.vue loads it after consent. The ID comes from + // NUXT_PUBLIC_SCRIPTS_GOOGLE_ANALYTICS_ID. + googleAnalytics: {}, + }, + }, +}) +``` + +Each registry key reads `NUXT_PUBLIC_SCRIPTS__` (camelCase key and field in SCREAMING_SNAKE_CASE) without a `runtimeConfig` declaration. +The key must be in `scripts.registry`; otherwise the module warns and ignores the variable. These values are public. + +## Automatic behaviour + +- SSR HTML contains no script tags. Scripts load on the client after hydration (`onNuxtReady`). +- SSR adds `` for `onNuxtReady` and `client` triggers. `warmupStrategy: false` stops it for `onNuxtReady`; a `client` trigger always preloads. +- Cross-origin tags get `crossorigin="anonymous"` and `referrerpolicy="no-referrer"`. +- Registry scripts that support bundling are downloaded at build and served from `/_scripts/assets/.js`. Set `bundle: false` on an entry to load from the vendor. For most scripts this also stops the proxy, because the proxy rewrites the bundle. +- Collection requests of supported scripts go through `/_scripts/p//...`. Each script has a privacy tier, and every tier anonymises the IP. Set `proxy: false` on an entry to send them direct. +- Calls with the same `key`, else `src`, share one script. A second call returns the shared instance and ignores its input, `use`, and `resolve`. Its `trigger` still runs, and a call with no trigger uses `onNuxtReady`. +- Each call gets its own consumer scope. Unmounting the component releases that call's callbacks and triggers; the shared script stays. + +## Common tasks + +Load consent-gated scripts. A module-level trigger in `app/utils/` is shared by every component: + +```ts +// app/utils/consent.ts +import { useScriptTriggerConsent } from '#imports' + +export const scriptsConsent = useScriptTriggerConsent() +``` + +```vue + + + + +``` + +Pass `scriptOptions: { trigger: scriptsConsent }` to every call of a consent-gated script, such as a component that tracks an event. One call without it loads the script before consent. +`scriptsConsent.revoke()` sets `consented` to `false` but does not unload a loaded script. Call the vendor API through `consent` instead. + +Call a vendor API. Proxy calls queue until load and return `undefined`; use `onLoaded` for a return value: + +```vue + + + + +``` + +If the SDK signals readiness through a callback, use `resolve({ waitFor })`. `load()` then resolves only when the callback fires. The returned cleanup runs when the callback fires, or earlier if the script lifecycle ends: + +```vue + + + + +``` + +Give each instance a unique `key` to load one script twice with different options. + +## Integrations + +- `partytown: true` needs `@nuxtjs/partytown`. It writes `