diff --git a/.claude/skills/skilld-lock.yaml b/.claude/skills/skilld-lock.yaml new file mode 100644 index 00000000..c674e659 --- /dev/null +++ b/.claude/skills/skilld-lock.yaml @@ -0,0 +1,8 @@ +skills: + unplugin-skilld: + packageName: unplugin + version: 3.0.0 + repo: unjs/unplugin + source: "https://github.com/unjs/unplugin/tree/v3.0.0/docs" + syncedAt: 2026-02-19 + generator: skilld diff --git a/.claude/skills/unplugin-skilld/SKILL.md b/.claude/skills/unplugin-skilld/SKILL.md new file mode 100644 index 00000000..b60e0017 --- /dev/null +++ b/.claude/skills/unplugin-skilld/SKILL.md @@ -0,0 +1,29 @@ +--- +name: unplugin-skilld +description: "Unified plugin system for build tools. ALWAYS use when writing code importing \"unplugin\". Consult for debugging, best practices, or modifying unplugin." +metadata: + version: 3.0.0 + generated_at: 2026-02-19 +--- + +# unjs/unplugin `unplugin` + +> Unified plugin system for build tools + +**Version:** 3.0.0 (Jan 2026) +**Deps:** @jridgewell/remapping@^2.3.5, picomatch@^4.0.3, webpack-virtual-modules@^0.6.2 +**Tags:** latest-v1: 1.16.1 (Jan 2025), latest-v2: 2.3.10 (Aug 2025), beta: 3.0.0-beta.3 (Nov 2025), latest: 3.0.0 (Jan 2026) + +**References:** [package.json](./.skilld/pkg/package.json) — exports, entry points • [README](./.skilld/pkg/README.md) — setup, basic usage • [Docs](./.skilld/docs/_INDEX.md) — API reference, guides • [GitHub Issues](./.skilld/issues/_INDEX.md) — bugs, workarounds, edge cases • [Releases](./.skilld/releases/_INDEX.md) — changelog, breaking changes, new APIs + +## Search + +Use `npx -y skilld search` instead of grepping `.skilld/` directories — hybrid semantic + keyword search across all indexed docs, issues, and releases. + +```bash +npx -y skilld search "query" -p unplugin +npx -y skilld search "issues:error handling" -p unplugin +npx -y skilld search "releases:deprecated" -p unplugin +``` + +Filters: `docs:`, `issues:`, `releases:` prefix narrows by source type. diff --git a/.github/ISSUE_TEMPLATE/bug-report.yml b/.github/ISSUE_TEMPLATE/bug-report.yml index 88d2eb0f..7e1ea0ff 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.yml +++ b/.github/ISSUE_TEMPLATE/bug-report.yml @@ -13,7 +13,7 @@ body: required: true attributes: label: 🛠️ To reproduce - description: "A reproduction of the bug. Please use the Nuxt Scripts starter https://stackblitz.com/edit/nuxt-starter-pkwfkx?file=pages%2Findex.vue" + description: 'A reproduction of the bug. Please use the Nuxt Scripts starter https://stackblitz.com/edit/nuxt-starter-pkwfkx?file=pages%2Findex.vue' placeholder: https://stackblitz.com/[...] - type: textarea validations: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e6c1a1ae..9db81c0e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,50 +1,54 @@ -name: ci +name: Test on: push: - branches: - - main - pull_request: - branches: - - main + paths-ignore: + - '**/README.md' + - 'docs/**' -permissions: {contents: read} +permissions: + contents: read jobs: - ci: + test: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - run: corepack enable - - uses: actions/setup-node@v4 + - uses: actions/checkout@v6 + + - uses: pnpm/action-setup@v4 + + - uses: actions/setup-node@v6 with: - node-version: 18 + node-version: lts/* cache: pnpm - - run: pnpm install + + - run: pnpm i + + - name: Lint + run: pnpm lint + + - name: Prepare + run: pnpm dev:prepare # https://github.com/vitejs/vite/blob/main/.github/workflows/ci.yml#L62 # Install playwright's binary under custom directory to cache - - name: Set Playwright path (non-windows) - if: runner.os != 'Windows' + - name: Set Playwright path run: echo "PLAYWRIGHT_BROWSERS_PATH=$HOME/.cache/playwright-bin" >> $GITHUB_ENV - - name: Set Playwright path (windows) - if: runner.os == 'Windows' - run: echo "PLAYWRIGHT_BROWSERS_PATH=$HOME\.cache\playwright-bin" >> $env:GITHUB_ENV - name: Cache Playwright's binary - uses: actions/cache@v4 + uses: actions/cache@v5 with: - # Playwright removes unused browsers automatically - # So does not need to add playwright version to key key: ${{ runner.os }}-playwright-bin-v1 path: ${{ env.PLAYWRIGHT_BROWSERS_PATH }} - name: Install Playwright - # does not need to explicitly set chromium after https://github.com/microsoft/playwright/issues/14862 is solved run: pnpm playwright-core install chromium - - run: pnpm dev:prepare - - run: pnpm lint - - run: pnpm test - - run: pnpm build - - run: pnpm typecheck + - name: Typecheck + run: pnpm typecheck + + - name: Build + run: pnpm build + + - name: Test + run: pnpm vitest run --project typecheck --project unit --project e2e --project nuxt-runtime diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml new file mode 100644 index 00000000..486a09d5 --- /dev/null +++ b/.github/workflows/nightly.yml @@ -0,0 +1,37 @@ +name: Nightly + +on: + pull_request: + push: + branches: + - main + tags: + - '!**' + +permissions: + contents: read # access to check out code and install dependencies + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + + - name: Install pnpm + uses: pnpm/action-setup@v4 + + - name: Use Node.js LTS + uses: actions/setup-node@v6 + with: + node-version: lts/* + registry-url: https://registry.npmjs.org/ + cache: pnpm + + - run: pnpm i + + - name: Build + run: pnpm run build + + - name: Publish + run: pnpm dlx pkg-pr-new publish diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0674ce39..e5c26cf7 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,35 +1,40 @@ -name: release +name: Release + +permissions: + contents: write + id-token: write on: push: - branches: - - main - -permissions: {} + tags: + - 'v*' jobs: release: runs-on: ubuntu-latest - if: ${{ github.repository_owner == 'nuxt' && github.event_name == 'push' }} - permissions: - id-token: write steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - - run: corepack enable - - uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3 + - uses: actions/checkout@v6 with: - node-version: 20 - registry-url: 'https://registry.npmjs.org/' - cache: pnpm + fetch-depth: 0 + + - name: Install pnpm + uses: pnpm/action-setup@v4 - - name: Install dependencies - run: pnpm install + - name: Set node + uses: actions/setup-node@v6 + with: + node-version: latest + cache: pnpm + registry-url: 'https://registry.npmjs.org' - - name: Prepare build environment - run: pnpm dev:prepare + - name: Force Set pnpm Registry + run: pnpm config set registry https://registry.npmjs.org - - name: nightly release - run: pnpm changelogen --canary nightly --publish + - run: npx changelogithub env: - NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }} - NPM_CONFIG_PROVENANCE: true + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} + + - name: Install Dependencies + run: pnpm i + + - run: pnpm publish -r --access public --no-git-checks --tag beta diff --git a/.github/workflows/reproduction-close.yml b/.github/workflows/reproduction-close.yml index f49969a7..f0adf225 100644 --- a/.github/workflows/reproduction-close.yml +++ b/.github/workflows/reproduction-close.yml @@ -11,7 +11,7 @@ jobs: stale: runs-on: ubuntu-latest steps: - - uses: actions/stale@28ca1036281a5e5922ead5184a1bbf96e5fc984e # v9.0.0 + - uses: actions/stale@3a9db7e6a41a89f618792c92c0e97cc736e1b13f # v10.0.0 with: days-before-stale: -1 # Issues and PR will never be flagged stale automatically. stale-issue-label: needs reproduction # Label that flags an issue as stale. diff --git a/.github/workflows/reproduction.yml b/.github/workflows/reproduction.yml index e38add83..5de84ad8 100644 --- a/.github/workflows/reproduction.yml +++ b/.github/workflows/reproduction.yml @@ -10,7 +10,7 @@ jobs: reproduire: runs-on: ubuntu-latest steps: - - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 + - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - uses: Hebilicious/reproduire@4b686ae9cbb72dad60f001d278b6e3b2ce40a9ac # v0.0.9-mp with: label: needs reproduction diff --git a/.gitignore b/.gitignore index e6d4c909..2116509f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,56 +1,39 @@ -# Dependencies node_modules - -# Logs +dist +.output +.idea +*.iml +*.swp +.DS_Store *.log* +coverage +.env +.env.* +!.env.example + +# Nuxt +.nuxt +playground/.nuxt +playground/.output +test/fixtures/**/.nuxt +test/fixtures/**/.output -# Temp directories +# Temp .temp .tmp .cache -# Yarn -**/.yarn/cache -**/.yarn/*state* - -# Generated dirs -dist - -# Nuxt -.nuxt -.output +# Build .vercel_build_output .build-* -.env .netlify -# Env -.env - # Testing reports -coverage *.lcov .nyc_output +test/fixtures/**/proxy-test.json +test/fixtures/**/sw-status.json -# VSCode -.vscode/* -!.vscode/settings.json -!.vscode/tasks.json -!.vscode/launch.json -!.vscode/extensions.json -!.vscode/*.code-snippets - -# Intellij idea -*.iml -.idea - -# OSX -.DS_Store -.AppleDouble -.LSOverride -.AppleDB -.AppleDesktop -Network Trash Folder -Temporary Items -.apdisk +# Skilld references (recreated by `skilld install`) +.skilld diff --git a/.nuxtrc b/.nuxtrc index 8ca389fa..485994ce 100644 --- a/.nuxtrc +++ b/.nuxtrc @@ -1 +1,3 @@ -imports.autoImport=false +imports.autoImport=true +modules.0="@nuxt/scripts" +setups.@nuxt/test-utils="4.0.0" \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 11d6e3ea..382797f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,896 @@ # Changelog +## v1.0.0-beta.6...main + +[compare changes](https://github.com/nuxt/scripts/compare/v1.0.0-beta.6...main) + +### 🩹 Fixes + +- **posthog:** 'history_change' as a capture_pageview option ([#620](https://github.com/nuxt/scripts/pull/620)) +- Preserve compressed/binary request bodies in proxy handler ([#619](https://github.com/nuxt/scripts/pull/619)) + +### 🏡 Chore + +- Bump deps ([4d79e91](https://github.com/nuxt/scripts/commit/4d79e91)) +- Fix rybbit loogo ([68eeb73](https://github.com/nuxt/scripts/commit/68eeb73)) + +### ❤️ Contributors + +- Harlan Wilton ([@harlan-zw](https://github.com/harlan-zw)) +- Kuba ([@zizzfizzix](https://github.com/zizzfizzix)) + +## v1.0.0-beta.5...main + +[compare changes](https://github.com/nuxt/scripts/compare/v1.0.0-beta.5...main) + +### 💅 Refactors + +- Replace SW + beacon monkey-patch with AST-based API rewriting ([#614](https://github.com/nuxt/scripts/pull/614)) + +### 🏡 Chore + +- Bump deps ([814bbf6](https://github.com/nuxt/scripts/commit/814bbf6)) + +### ❤️ Contributors + +- Harlan Wilton ([@harlan-zw](https://github.com/harlan-zw)) + +## v1.0.0-beta.4...main + +[compare changes](https://github.com/nuxt/scripts/compare/v1.0.0-beta.4...main) + +### 🩹 Fixes + +- Protect against broken new URL when proxying ([cb7ff7e](https://github.com/nuxt/scripts/commit/cb7ff7e)) + +### 🏡 Chore + +- Bump ([d03285a](https://github.com/nuxt/scripts/commit/d03285a)) + +### ❤️ Contributors + +- Harlan Wilton ([@harlan-zw](https://github.com/harlan-zw)) + +## v1.0.0-beta.3...main + +[compare changes](https://github.com/nuxt/scripts/compare/v1.0.0-beta.3...main) + +### 🩹 Fixes + +- Broken bundle transforms ([84709eb](https://github.com/nuxt/scripts/commit/84709eb)) + +### 🏡 Chore + +- Bump ([4a23752](https://github.com/nuxt/scripts/commit/4a23752)) + +### ❤️ Contributors + +- Harlan Wilton ([@harlan-zw](https://github.com/harlan-zw)) + +## v1.0.0-beta.2...main + +[compare changes](https://github.com/nuxt/scripts/compare/v1.0.0-beta.2...main) + +### 🚀 Enhancements + +- **posthog:** Support proxy mode ([6c4675f](https://github.com/nuxt/scripts/commit/6c4675f)) +- **proxy:** Granular per-script privacy controls ([#611](https://github.com/nuxt/scripts/pull/611)) + +### 🩹 Fixes + +- Broken bare domain path matching ([58fe9e3](https://github.com/nuxt/scripts/commit/58fe9e3)) + +### 🏡 Chore + +- Bump deps ([ff0135e](https://github.com/nuxt/scripts/commit/ff0135e)) + +### ❤️ Contributors + +- Harlan Wilton ([@harlan-zw](https://github.com/harlan-zw)) + +## v1.0.0-beta.1...main + +[compare changes](https://github.com/nuxt/scripts/compare/v1.0.0-beta.1...main) + +### 🚀 Enhancements + +- Add SSR social media embeds for X and Instagram ([#590](https://github.com/nuxt/scripts/pull/590)) +- Experimental nuxt/partytown support ([#576](https://github.com/nuxt/scripts/pull/576)) +- First-party proxy mode with privacy anonymization ([#577](https://github.com/nuxt/scripts/pull/577)) + +### 🔥 Performance + +- Migrate plugins to oxc-walker (parseAndWalk) ([#610](https://github.com/nuxt/scripts/pull/610)) + +### 🩹 Fixes + +- Safer resolve of triggers via nuxt.config ([738703a](https://github.com/nuxt/scripts/commit/738703a)) +- Import from `@nuxt/schema` ([#600](https://github.com/nuxt/scripts/pull/600)) +- Explicit opt-in for proxy mode ([ef5f418](https://github.com/nuxt/scripts/commit/ef5f418)) +- Migrate to unplugin v3 ([409a88c](https://github.com/nuxt/scripts/commit/409a88c)) +- **proxy:** Missing path separators ([a935e97](https://github.com/nuxt/scripts/commit/a935e97)) +- Better registry key matching ([23c7545](https://github.com/nuxt/scripts/commit/23c7545)) +- **proxy:** Missing script src ([fadda86](https://github.com/nuxt/scripts/commit/fadda86)) +- **tiktok:** Missing global ([8b39140](https://github.com/nuxt/scripts/commit/8b39140)) + +### 📖 Documentation + +- Add skilld agent skill snippet ([#603](https://github.com/nuxt/scripts/pull/603)) + +### 🏡 Chore + +- Broken type checks ([c5e8c84](https://github.com/nuxt/scripts/commit/c5e8c84)) +- Missed files ([0485d59](https://github.com/nuxt/scripts/commit/0485d59)) +- Sync ([eaf6620](https://github.com/nuxt/scripts/commit/eaf6620)) +- Disable proxy service worker in dev ([473894b](https://github.com/nuxt/scripts/commit/473894b)) +- Bump deps ([b0a5dca](https://github.com/nuxt/scripts/commit/b0a5dca)) +- Bump deps ([989e90d](https://github.com/nuxt/scripts/commit/989e90d)) +- Broken tests ([e96c0d3](https://github.com/nuxt/scripts/commit/e96c0d3)) +- Broken tests ([97d72a4](https://github.com/nuxt/scripts/commit/97d72a4)) +- Broken tests ([8dd59f5](https://github.com/nuxt/scripts/commit/8dd59f5)) +- Broken tests ([9c4662e](https://github.com/nuxt/scripts/commit/9c4662e)) +- Sync ([4290e7e](https://github.com/nuxt/scripts/commit/4290e7e)) +- Sync ([4db19a8](https://github.com/nuxt/scripts/commit/4db19a8)) +- Sync ([90ecc42](https://github.com/nuxt/scripts/commit/90ecc42)) + +### ❤️ Contributors + +- Harlan Wilton ([@harlan-zw](https://github.com/harlan-zw)) +- Daniel Roe ([@danielroe](https://github.com/danielroe)) + +## v0.13.1...main + +[compare changes](https://github.com/nuxt/scripts/compare/v0.13.1...main) + +### 🩹 Fixes + +- Allow `@stripe/stripe-js` v8 ([f516827](https://github.com/nuxt/scripts/commit/f516827)) +- **paypal:** V9 sdk peer dependency ([a8a93f4](https://github.com/nuxt/scripts/commit/a8a93f4)) + +### 🏡 Chore + +- Changelog ([6d7b22a](https://github.com/nuxt/scripts/commit/6d7b22a)) +- Bump ([9909791](https://github.com/nuxt/scripts/commit/9909791)) + +### ❤️ Contributors + +- Harlan Wilton ([@harlan-zw](https://github.com/harlan-zw)) + +## v0.13.0...main + +[compare changes](https://github.com/nuxt/scripts/compare/v0.13.0...main) + +### 🩹 Fixes + +- **x-pixel:** Correct variable name from e.exe to s.exe ([#541](https://github.com/nuxt/scripts/pull/541)) +- Convert MarkerClusterer to dynamic import to support optional peer dependency ([#551](https://github.com/nuxt/scripts/pull/551)) +- **google-maps:** Missing types for `` ([#560](https://github.com/nuxt/scripts/pull/560)) + +### 💅 Refactors + +- Use `extendViteConfig` in development ([#554](https://github.com/nuxt/scripts/pull/554)) + +### 📖 Documentation + +- Correct weird wording ([#538](https://github.com/nuxt/scripts/pull/538)) +- Destruct TrackedPage payload ([#549](https://github.com/nuxt/scripts/pull/549)) +- Fix Nuxt badge icon ([#559](https://github.com/nuxt/scripts/pull/559)) + +### 🏡 Chore + +- Bump ([d825df9](https://github.com/nuxt/scripts/commit/d825df9)) +- Vitest v4 fixes ([2053869](https://github.com/nuxt/scripts/commit/2053869)) + +### ❤️ Contributors + +- Harlan Wilton ([@harlan-zw](https://github.com/harlan-zw)) +- Bobbie Goede +- IO-Fire ([@IO-Fire](https://github.com/IO-Fire)) +- Copilot ([@MicrosoftCopilot](https://github.com/MicrosoftCopilot)) +- Daniel Roe ([@danielroe](https://github.com/danielroe)) +- Dennis Adriaansen +- Kohei Tsukiyama ([@tsukiyama-3](https://github.com/tsukiyama-3)) +- Alexandru Ungureanu ([@unguul](https://github.com/unguul)) + +## v0.12.2...main + +[compare changes](https://github.com/nuxt/scripts/compare/v0.12.2...main) + +### 🚀 Enhancements + +- ⚠️ Plausible script updates ([#534](https://github.com/nuxt/scripts/pull/534)) + +### 🩹 Fixes + +- **google-maps:** Mark peer dependency as optional ([#537](https://github.com/nuxt/scripts/pull/537)) +- DefaultScriptOptions idleTimeout config ([faf83ff](https://github.com/nuxt/scripts/commit/faf83ff)) + +### 🏡 Chore + +- Sync changelog ([da14392](https://github.com/nuxt/scripts/commit/da14392)) +- Bump ([e1a02d3](https://github.com/nuxt/scripts/commit/e1a02d3)) + +#### ⚠️ Breaking Changes + +- ⚠️ Plausible script updates ([#534](https://github.com/nuxt/scripts/pull/534)) + +### ❤️ Contributors + +- Harlan Wilton ([@harlan-zw](https://github.com/harlan-zw)) +- Danshil Kokil Mungur + +## v0.12.1...main + +[compare changes](https://github.com/nuxt/scripts/compare/v0.12.1...main) + +### 🩹 Fixes + +- Databuddy types script registry ([#528](https://github.com/nuxt/scripts/pull/528)) +- **gtm:** Expose `gtag` globally ([#524](https://github.com/nuxt/scripts/pull/524)) +- Add missing autoImports ([#532](https://github.com/nuxt/scripts/pull/532)) + +### 🏡 Chore + +- Bump ([927326d](https://github.com/nuxt/scripts/commit/927326d)) +- Bump ([fb10711](https://github.com/nuxt/scripts/commit/fb10711)) + +### ❤️ Contributors + +- Harlan Wilton ([@harlan-zw](https://github.com/harlan-zw)) +- Julien Huang ([@huang-julien](https://github.com/huang-julien)) +- DanLdu ([@DanLDU](https://github.com/DanLDU)) +- Vachmara + +## v0.12.0...main + +[compare changes](https://github.com/nuxt/scripts/compare/v0.12.0...main) + +### 🔥 Performance + +- **googleMaps:** Avoid multiple rerenders when adding markers to clusterer ([#517](https://github.com/nuxt/scripts/pull/517)) + +### 🩹 Fixes + +- Resolve import paths as absolute ([a217362](https://github.com/nuxt/scripts/commit/a217362)) + +### 🏡 Chore + +- Changelog ([61d694e](https://github.com/nuxt/scripts/commit/61d694e)) +- Sync ([da732bf](https://github.com/nuxt/scripts/commit/da732bf)) + +### ❤️ Contributors + +- Harlan Wilton ([@harlan-zw](https://github.com/harlan-zw)) +- Damian Głowala ([@DamianGlowala](https://github.com/DamianGlowala)) + +## v0.11.13...main + +[compare changes](https://github.com/nuxt/scripts/compare/v0.11.13...main) + +### 🚀 Enhancements + +- Add databuddy analytics ([#495](https://github.com/nuxt/scripts/pull/495)) +- PayPal SDK ([#503](https://github.com/nuxt/scripts/pull/503)) +- Reddit Pixel ([#507](https://github.com/nuxt/scripts/pull/507)) +- **stripe:** ⚠️ Stripe basil ([#509](https://github.com/nuxt/scripts/pull/509)) +- **google-maps:** Declarative SFC API ([#510](https://github.com/nuxt/scripts/pull/510)) +- Bundling cache expiration & bypass ([#497](https://github.com/nuxt/scripts/pull/497)) +- **devtools:** Improved tracking ([9aa000d](https://github.com/nuxt/scripts/commit/9aa000d)) +- **matomoAnalytics:** ⚠️ `watch` mode ([#514](https://github.com/nuxt/scripts/pull/514)) +- New event triggers ([#515](https://github.com/nuxt/scripts/pull/515)) + +### 🩹 Fixes + +- **devtools:** Show URL logos correctly ([30abad4](https://github.com/nuxt/scripts/commit/30abad4)) +- **devtools:** Show error status's ([22d91c5](https://github.com/nuxt/scripts/commit/22d91c5)) +- **devtools:** Show validation errors ([c9e1a20](https://github.com/nuxt/scripts/commit/c9e1a20)) +- **devtools:** Add null safety ([#501](https://github.com/nuxt/scripts/pull/501)) +- Merge query parms when overriding `scriptInput.src` ([#500](https://github.com/nuxt/scripts/pull/500)) +- **gtm:** Broken `onBeforeGtmStart` arguments ([#494](https://github.com/nuxt/scripts/pull/494)) +- **types:** Publish types for node context ([695b7f1](https://github.com/nuxt/scripts/commit/695b7f1)) +- **matomo:** Broken event tracking ([712a869](https://github.com/nuxt/scripts/commit/712a869)) +- Gracefully handle production only scripts ([a53ab33](https://github.com/nuxt/scripts/commit/a53ab33)) +- Drop `#imports` usage ([67b7e70](https://github.com/nuxt/scripts/commit/67b7e70)) +- Drop `performanceMarkFeature` emits ([c079f51](https://github.com/nuxt/scripts/commit/c079f51)) +- **googleAnalytics:** Support customer ids ([20aa9d5](https://github.com/nuxt/scripts/commit/20aa9d5)) +- Respect env data when bundling ([864102f](https://github.com/nuxt/scripts/commit/864102f)) +- Improved debugging when bundling fails ([34d7be7](https://github.com/nuxt/scripts/commit/34d7be7)) +- **useScriptEventPage:** Remove hooks on dispose ([cf5d715](https://github.com/nuxt/scripts/commit/cf5d715)) + +### 📖 Documentation + +- Improve responsiveness ([#505](https://github.com/nuxt/scripts/pull/505)) +- Improve bundling documentation ([#498](https://github.com/nuxt/scripts/pull/498)) + +### 🏡 Chore + +- Type issue ([fe6ea30](https://github.com/nuxt/scripts/commit/fe6ea30)) +- Changelog ([83e9954](https://github.com/nuxt/scripts/commit/83e9954)) +- Bump deps ([110eaa9](https://github.com/nuxt/scripts/commit/110eaa9)) +- Bump lock ([57ea493](https://github.com/nuxt/scripts/commit/57ea493)) +- Missing paypal type ([3c01eb8](https://github.com/nuxt/scripts/commit/3c01eb8)) +- Bump deps ([511b57f](https://github.com/nuxt/scripts/commit/511b57f)) +- Types ([1ae8043](https://github.com/nuxt/scripts/commit/1ae8043)) +- Standalone playground ([8662c66](https://github.com/nuxt/scripts/commit/8662c66)) +- Tidy up playground ([dbed5b7](https://github.com/nuxt/scripts/commit/dbed5b7)) +- Sync playground ([6571544](https://github.com/nuxt/scripts/commit/6571544)) +- Lint ([b7213e0](https://github.com/nuxt/scripts/commit/b7213e0)) +- Lint ([78fbd7c](https://github.com/nuxt/scripts/commit/78fbd7c)) +- Lint ([6361c68](https://github.com/nuxt/scripts/commit/6361c68)) +- Broken tests ([ba85f61](https://github.com/nuxt/scripts/commit/ba85f61)) +- Bump deps ([a27120d](https://github.com/nuxt/scripts/commit/a27120d)) +- Lint ([ecada33](https://github.com/nuxt/scripts/commit/ecada33)) +- Fix broken test ([9dd054f](https://github.com/nuxt/scripts/commit/9dd054f)) +- Bump deps ([b98c236](https://github.com/nuxt/scripts/commit/b98c236)) +- Maybe fix nightly ([b9f6727](https://github.com/nuxt/scripts/commit/b9f6727)) + +#### ⚠️ Breaking Changes + +- **stripe:** ⚠️ Stripe basil ([#509](https://github.com/nuxt/scripts/pull/509)) +- **matomoAnalytics:** ⚠️ `watch` mode ([#514](https://github.com/nuxt/scripts/pull/514)) + +### ❤️ Contributors + +- Harlan Wilton ([@harlan-zw](https://github.com/harlan-zw)) +- René Kersten +- Gelanderos +- Vachmara + +## v0.11.12...main + +[compare changes](https://github.com/nuxt/scripts/compare/v0.11.12...main) + +### 🏡 Chore + +- Broken release script ([3e6fc59](https://github.com/nuxt/scripts/commit/3e6fc59)) + +### ❤️ Contributors + +- Harlan Wilton ([@harlan-zw](https://github.com/harlan-zw)) + +## v0.11.10...main + +[compare changes](https://github.com/nuxt/scripts/compare/v0.11.10...main) + +### 🏡 Chore + +- NPM trusted publishing ([b785b61](https://github.com/nuxt/scripts/commit/b785b61)) +- Sync changelog ([032f73c](https://github.com/nuxt/scripts/commit/032f73c)) +- Bump deps ([abfa89e](https://github.com/nuxt/scripts/commit/abfa89e)) +- Sync lock ([f26b0a2](https://github.com/nuxt/scripts/commit/f26b0a2)) +- Broken release script ([c79c45a](https://github.com/nuxt/scripts/commit/c79c45a)) + +### ❤️ Contributors + +- Harlan Wilton ([@harlan-zw](https://github.com/harlan-zw)) + +## v0.11.9...main + +[compare changes](https://github.com/nuxt/scripts/compare/v0.11.9...main) + +### 🏡 Chore + +- Migrate to `addTypeTemplate` ([399e0c2](https://github.com/nuxt/scripts/commit/399e0c2)) +- Add nightly releases ([e49434e](https://github.com/nuxt/scripts/commit/e49434e)) +- Drop nuxt peer dependency ([dc4e84a](https://github.com/nuxt/scripts/commit/dc4e84a)) +- Missing sqlite dep ([922467c](https://github.com/nuxt/scripts/commit/922467c)) + +### ❤️ Contributors + +- Harlan Wilton ([@harlan-zw](https://github.com/harlan-zw)) + +## v0.11.8...main + +[compare changes](https://github.com/nuxt/scripts/compare/v0.11.8...main) + +### 🚀 Enhancements + +- **rybbit:** Add new tracking configuration options ([#471](https://github.com/nuxt/scripts/pull/471)) + +### 🩹 Fixes + +- **meta-pixel:** Refine fbq type definitions ([#464](https://github.com/nuxt/scripts/pull/464)) +- Support cdnURL for bundled scripts ([#472](https://github.com/nuxt/scripts/pull/472)) + +### 📖 Documentation + +- Correct contents of XPixelAPI ([#465](https://github.com/nuxt/scripts/pull/465)) + +### 🏡 Chore + +- Bump deps & changelog ([19c74b2](https://github.com/nuxt/scripts/commit/19c74b2)) +- Bump lock ([fb2d62b](https://github.com/nuxt/scripts/commit/fb2d62b)) +- Remove `bridge: false` from module compatibility ([#470](https://github.com/nuxt/scripts/pull/470)) +- Fix tests ([e14cd1b](https://github.com/nuxt/scripts/commit/e14cd1b)) +- Fix tests ([2b7002c](https://github.com/nuxt/scripts/commit/2b7002c)) +- Fix tests ([9f486ae](https://github.com/nuxt/scripts/commit/9f486ae)) +- Type issues ([243d8d6](https://github.com/nuxt/scripts/commit/243d8d6)) +- Sync lock ([0198225](https://github.com/nuxt/scripts/commit/0198225)) + +### ❤️ Contributors + +- Harlan Wilton ([@harlan-zw](https://github.com/harlan-zw)) +- Dan +- Daniel Roe ([@danielroe](https://github.com/danielroe)) +- Nagaakihoshi +- Kohei Tsukiyama ([@tsukiyama-3](https://github.com/tsukiyama-3)) + +## v0.11.7...main + +[compare changes](https://github.com/nuxt/scripts/compare/v0.11.7...main) + +### 🩹 Fixes + +- **GoogleMaps:** Properly handle centerMarker removal logic ([#460](https://github.com/nuxt/scripts/pull/460)) +- **rybbit:** Support number `siteId` ([ab89f42](https://github.com/nuxt/scripts/commit/ab89f42)) +- **rybbit:** Prefer top level functions ([8853333](https://github.com/nuxt/scripts/commit/8853333)) +- **UmamiAnalytics:** V2.18 compatibility ([7671c80](https://github.com/nuxt/scripts/commit/7671c80)) +- **VimeoPlayer:** Switch to new oembed API for thumbnails ([09fa69f](https://github.com/nuxt/scripts/commit/09fa69f)) + +### 📖 Documentation + +- Add manual ContentSurround for the introduction step ([#456](https://github.com/nuxt/scripts/pull/456)) + +### 🏡 Chore + +- Fix doc build ([8889c58](https://github.com/nuxt/scripts/commit/8889c58)) +- Missing lock file ([7f4da53](https://github.com/nuxt/scripts/commit/7f4da53)) +- Broken components ([1d72679](https://github.com/nuxt/scripts/commit/1d72679)) +- Docs overflow bug ([ab52c85](https://github.com/nuxt/scripts/commit/ab52c85)) +- **rybbit:** Improve types ([93e2971](https://github.com/nuxt/scripts/commit/93e2971)) +- Bump deps ([f9d9cc9](https://github.com/nuxt/scripts/commit/f9d9cc9)) + +### ❤️ Contributors + +- Harlan Wilton ([@harlan-zw](https://github.com/harlan-zw)) +- Ilya Shaplyko ([@Shaglock](https://github.com/Shaglock)) +- Faudel HADROUG ([@Faudelhadroug](https://github.com/Faudelhadroug)) + +## v0.11.6...main + +[compare changes](https://github.com/nuxt/scripts/compare/v0.11.6...main) + +### 🚀 Enhancements + +- Add rybbit analytics to registry ([#453](https://github.com/nuxt/scripts/pull/453)) + +### 🩹 Fixes + +- **GoogleMaps:** Center marker always shown on maps placeholder image #402 ([#454](https://github.com/nuxt/scripts/pull/454), [#402](https://github.com/nuxt/scripts/issues/402)) + +### 🏡 Chore + +- Bump changelog ([5d1403e](https://github.com/nuxt/scripts/commit/5d1403e)) +- Bump deps ([756705a](https://github.com/nuxt/scripts/commit/756705a)) +- Fix tests ([cc6f7cf](https://github.com/nuxt/scripts/commit/cc6f7cf)) + +### ❤️ Contributors + +- Harlan Wilton ([@harlan-zw](https://github.com/harlan-zw)) +- Dan +- Ilya Shaplyko ([@Shaglock](https://github.com/Shaglock)) + +## v0.11.5 + +[compare changes](https://github.com/nuxt/scripts/compare/v0.11.5...main) + +### 🩹 Fixes + +- **snapchatPixel:** Broken option merging when mocked ([49f7dff](https://github.com/nuxt/scripts/commit/49f7dff)) +- **bundling:** Avoid bundling scripts that are missing options ([e4b4032](https://github.com/nuxt/scripts/commit/e4b4032)) +- **clarity:** Proxying broken once clarity loads ([8469df9](https://github.com/nuxt/scripts/commit/8469df9)) + +### 🏡 Chore + +- Bump deps ([213619e](https://github.com/nuxt/scripts/commit/213619e)) +- Maybe fix typecheck ([d4d8f31](https://github.com/nuxt/scripts/commit/d4d8f31)) +- Update release script ([e75bdaf](https://github.com/nuxt/scripts/commit/e75bdaf)) + +### ❤️ Contributors + +- Harlan Wilton ([@harlan-zw](https://github.com/harlan-zw)) + +## v0.9.7 + +[compare changes](https://github.com/nuxt/scripts/compare/v0.9.6...v0.9.7) + +## v0.9.6 + +[compare changes](https://github.com/nuxt/scripts/compare/v0.9.5...v0.9.6) + +### 🚀 Enhancements + +- **YouTubePlayer:** Adjust thumbnail ratio to 640x360 ([#310](https://github.com/nuxt/scripts/pull/310)) +- **google-adsense:** Add Auto Ads support and improve script injection ([#366](https://github.com/nuxt/scripts/pull/366)) +- **useScriptNpm:** Support multiple providers with validation ([#353](https://github.com/nuxt/scripts/pull/353)) +- **youtube:** `thumbnailSize` prop with fallback support ([#376](https://github.com/nuxt/scripts/pull/376)) +- Add umami analytics to registry ([#348](https://github.com/nuxt/scripts/pull/348)) +- Add Snapchat pixel to registry ([#337](https://github.com/nuxt/scripts/pull/337)) + +### 🩹 Fixes + +- `nuxtApp.$scripts` types ([#303](https://github.com/nuxt/scripts/pull/303)) +- **CarbonAds:** Avoid duplicate emits ([c93bd22](https://github.com/nuxt/scripts/commit/c93bd22)) +- **CarbonAds:** Unnecessary script type ([325cde1](https://github.com/nuxt/scripts/commit/325cde1)) +- **CarbonAds:** Missing `format` prop ([#315](https://github.com/nuxt/scripts/pull/315)) +- Move `#nuxt-scripts` alias path ([2a1ab47](https://github.com/nuxt/scripts/commit/2a1ab47)) +- **YoutubePlayer,VimeoPlayer:** ⚠️ Auto width for responsive design on mobile devices ([#341](https://github.com/nuxt/scripts/pull/341)) +- ⚠️ Drop type dependencies ([f545526](https://github.com/nuxt/scripts/commit/f545526)) +- Download scripts using `$fetch` with retries ([39c931e](https://github.com/nuxt/scripts/commit/39c931e)) +- Prefer explicit imports over `#imports` ([a9af35a](https://github.com/nuxt/scripts/commit/a9af35a)) +- Drop `third-party-capital` ([63e78d2](https://github.com/nuxt/scripts/commit/63e78d2)) +- **youtube:** Default host `youtube-nocookie.com` ([d814c7e](https://github.com/nuxt/scripts/commit/d814c7e)) +- Prefer invisible screen reader loading indicator ([ddc88a4](https://github.com/nuxt/scripts/commit/ddc88a4)) +- **adsense:** Use globally configured `client` in component ([3f7e408](https://github.com/nuxt/scripts/commit/3f7e408)) + +### 💅 Refactors + +- Remove TPC composable generation ([#368](https://github.com/nuxt/scripts/pull/368)) + +### 📖 Documentation + +- Fix typo for google analytics ([#317](https://github.com/nuxt/scripts/pull/317)) +- Fix inconsistent example env keys ([#331](https://github.com/nuxt/scripts/pull/331)) +- Improve incorrect example of using useScriptTriggerElement ([#362](https://github.com/nuxt/scripts/pull/362)) + +### 📦 Build + +- Set a resolution for consola ([#369](https://github.com/nuxt/scripts/pull/369)) + +### 🏡 Chore + +- Bump deps ([c6831d7](https://github.com/nuxt/scripts/commit/c6831d7)) +- Bump deps ([7a7a8e6](https://github.com/nuxt/scripts/commit/7a7a8e6)) +- Bump deps ([a68b5dc](https://github.com/nuxt/scripts/commit/a68b5dc)) +- Maybe fix tests ([70de7c8](https://github.com/nuxt/scripts/commit/70de7c8)) +- Bump deps ([e83499e](https://github.com/nuxt/scripts/commit/e83499e)) +- Lock broken ([181ab24](https://github.com/nuxt/scripts/commit/181ab24)) +- Avoid ts 5.7 bug ([e9698ae](https://github.com/nuxt/scripts/commit/e9698ae)) +- Bump deps ([2d2f96a](https://github.com/nuxt/scripts/commit/2d2f96a)) +- Sync lock ([7e1477c](https://github.com/nuxt/scripts/commit/7e1477c)) +- Bump deps ([e8e2a7b](https://github.com/nuxt/scripts/commit/e8e2a7b)) +- Fix release ([c335e61](https://github.com/nuxt/scripts/commit/c335e61)) +- Broken tests ([443aad5](https://github.com/nuxt/scripts/commit/443aad5)) +- Sync lock ([0ad28bc](https://github.com/nuxt/scripts/commit/0ad28bc)) +- **ci:** Broken install ([609ab91](https://github.com/nuxt/scripts/commit/609ab91)) +- Maybe fix tests ([4513ded](https://github.com/nuxt/scripts/commit/4513ded)) +- Maybe fix tests ([f2511a6](https://github.com/nuxt/scripts/commit/f2511a6)) +- Temp disable broken tests ([3b0c0fe](https://github.com/nuxt/scripts/commit/3b0c0fe)) +- Maybe fix release ([d5c8c70](https://github.com/nuxt/scripts/commit/d5c8c70)) +- Tests ([8d94549](https://github.com/nuxt/scripts/commit/8d94549)) +- Bump deps ([7e742de](https://github.com/nuxt/scripts/commit/7e742de)) +- Missing doc ([d7e1eae](https://github.com/nuxt/scripts/commit/d7e1eae)) +- Clean up ([f13bd26](https://github.com/nuxt/scripts/commit/f13bd26)) +- Clean up ([5ad0080](https://github.com/nuxt/scripts/commit/5ad0080)) + +### 🤖 CI + +- Remove corepack ([#372](https://github.com/nuxt/scripts/pull/372)) + +#### ⚠️ Breaking Changes + +- **YoutubePlayer,VimeoPlayer:** ⚠️ Auto width for responsive design on mobile devices ([#341](https://github.com/nuxt/scripts/pull/341)) +- ⚠️ Drop type dependencies ([f545526](https://github.com/nuxt/scripts/commit/f545526)) + +### ❤️ Contributors + +- Harlan ([@harlan-zw](http://github.com/harlan-zw)) +- Mod08 +- Mark1 ([@0ahz](http://github.com/0ahz)) +- Harlan Wilton ([@harlan-zw](http://github.com/harlan-zw)) +- Alfarish Fizikri +- Nexos Creator ([@nexoscreator](http://github.com/nexoscreator)) +- Julien Huang ([@huang-julien](http://github.com/huang-julien)) +- Jelmer ([@jelmerdemaat](http://github.com/jelmerdemaat)) +- Michael Brevard +- William Chong ([@williamchong](http://github.com/williamchong)) +- Daniel Roe ([@danielroe](http://github.com/danielroe)) +- Maxime Pauvert ([@maximepvrt](http://github.com/maximepvrt)) +- Rahul Vashishtha ([@rahul-vashishtha](http://github.com/rahul-vashishtha)) +- Mounir Bouaiche ([@b-mounir-dev](http://github.com/b-mounir-dev)) + +## v0.9.5 + +[compare changes](https://github.com/nuxt/scripts/compare/v0.9.4...v0.9.5) + +### 🚀 Enhancements + +- **googleMaps:** Language & region params ([#286](https://github.com/nuxt/scripts/pull/286)) +- Improved script warmup ([#302](https://github.com/nuxt/scripts/pull/302)) + +### 🩹 Fixes + +- Avoid warming delayed script src ([357d02a](https://github.com/nuxt/scripts/commit/357d02a)) +- **stripe:** Prefer `@stripe/stripe-js` over `@types/stripe-v3` ([#300](https://github.com/nuxt/scripts/pull/300)) +- Optional Valibot schema ([#287](https://github.com/nuxt/scripts/pull/287)) + +### 📖 Documentation + +- Fix typo/incomplete sentence ([#285](https://github.com/nuxt/scripts/pull/285)) + +### 🏡 Chore + +- Type issue ([6490ce3](https://github.com/nuxt/scripts/commit/6490ce3)) +- Type issue ([0c5135c](https://github.com/nuxt/scripts/commit/0c5135c)) +- Playground for multi datalayers ([#173](https://github.com/nuxt/scripts/pull/173)) +- Test scripts ([1f7df13](https://github.com/nuxt/scripts/commit/1f7df13)) +- Broken ci ([c89380f](https://github.com/nuxt/scripts/commit/c89380f)) +- **devtools:** Fix deprecation ([29864f1](https://github.com/nuxt/scripts/commit/29864f1)) +- Bump deps & lint ([c54412f](https://github.com/nuxt/scripts/commit/c54412f)) + +### ❤️ Contributors + +- Harlan ([@harlan-zw](http://github.com/harlan-zw)) +- Harlan Wilton ([@harlan-zw](http://github.com/harlan-zw)) +- Julien Huang ([@huang-julien](http://github.com/huang-julien)) +- Morgan-retex ([@morgan-retex](http://github.com/morgan-retex)) +- Stefano Bartoletti ([@stefanobartoletti](http://github.com/stefanobartoletti)) + +## v0.9.4 + +[compare changes](https://github.com/nuxt/scripts/compare/v0.9.3...v0.9.4) + +### 🩹 Fixes + +- **matomo:** Easier cloud config using `cloudId` ([d7e18c4](https://github.com/nuxt/scripts/commit/d7e18c4)) +- **matomo:** Support numeric `siteId` ([299516c](https://github.com/nuxt/scripts/commit/299516c)) +- Avoid overriding ` +- Julien Huang + +## v0.8.4 + +[compare changes](https://github.com/nuxt/scripts/compare/v0.8.3...v0.8.4) + +### 🩹 Fixes + +- **ScriptGoogleMaps:** Clean up map markers properly ([53bb530](https://github.com/nuxt/scripts/commit/53bb530)) + +### 🏡 Chore + +- Bump deps and lint ([06c757c](https://github.com/nuxt/scripts/commit/06c757c)) +- Sync lock ([bdc5f98](https://github.com/nuxt/scripts/commit/bdc5f98)) + +### ❤️ Contributors + +- Harlan + +## v0.8.3 + +[compare changes](https://github.com/nuxt/scripts/compare/v0.8.2...v0.8.3) + +### 🩹 Fixes + +- Do not omit `crossorigin` from link preload ([#241](https://github.com/nuxt/scripts/pull/241)) + +### 🏡 Chore + +- Bump deps ([cdebb4a](https://github.com/nuxt/scripts/commit/cdebb4a)) +- Fix tests ([45b5dc0](https://github.com/nuxt/scripts/commit/45b5dc0)) + +### ❤️ Contributors + +- Harlan +- Daniel Roe + +## v0.8.2 + +[compare changes](https://github.com/nuxt/scripts/compare/v0.8.1...v0.8.2) + +### 🚀 Enhancements + +- **matomo:** Support custom tracker urls ([#236](https://github.com/nuxt/scripts/pull/236)) + +### ❤️ Contributors + +- Reslear + +## v0.8.1 + +[compare changes](https://github.com/nuxt/scripts/compare/v0.8.0...v0.8.1) + +### 🩹 Fixes + +- Handle scripts missing `src` ([510d7b9](https://github.com/nuxt/scripts/commit/510d7b9)) + +### 📖 Documentation + +- Add learn section and carbon ads ([f4de446](https://github.com/nuxt/scripts/commit/f4de446)) + +### 🏡 Chore + +- Bump deps and lint ([03bd491](https://github.com/nuxt/scripts/commit/03bd491)) +- Fix test ([e3d78fd](https://github.com/nuxt/scripts/commit/e3d78fd)) + +### ❤️ Contributors + +- Harlan + +## v0.8.0 + +[compare changes](https://github.com/nuxt/scripts/compare/v0.7.3...v0.8.0) + +## v0.7.3 + +[compare changes](https://github.com/nuxt/scripts/compare/v0.7.2...v0.7.3) + +## v0.7.2 + +[compare changes](https://github.com/nuxt/scripts/compare/v0.7.1...v0.7.2) + +### 🚀 Enhancements + +- Automatically `preload` and `preconnect` relevant scripts ([a65a5e0](https://github.com/nuxt/scripts/commit/a65a5e0)) +- **useScriptTriggerElement:** Pre-hydration event triggers ([#237](https://github.com/nuxt/scripts/pull/237)) +- **googleMaps:** Unified styling of static image and map ([c85d278](https://github.com/nuxt/scripts/commit/c85d278)) + +### 🩹 Fixes + +- Soft-dependency on Unhead v1.10.1 ([4a9fc40](https://github.com/nuxt/scripts/commit/4a9fc40)) +- Allow `useScript` to re-register trigger ([9890124](https://github.com/nuxt/scripts/commit/9890124)) +- **useScriptTriggerElement:** Reject promises on scope dispose ([7297783](https://github.com/nuxt/scripts/commit/7297783)) +- **googleAdsense:** Broken validation input path ([f198a80](https://github.com/nuxt/scripts/commit/f198a80)) + +### 💅 Refactors + +- **playground:** Avoid deprecated useScript api ([0f02696](https://github.com/nuxt/scripts/commit/0f02696)) + +### 📖 Documentation + +- Add coding group end to plausible page ([#215](https://github.com/nuxt/scripts/pull/215)) + +### 🏡 Chore + +- **release:** V0.7.1 ([48e4244](https://github.com/nuxt/scripts/commit/48e4244)) +- Bump nuxt version ([#224](https://github.com/nuxt/scripts/pull/224)) +- **docs:** Fix incorrect links ([#228](https://github.com/nuxt/scripts/pull/228)) +- Bump deps ([13184f8](https://github.com/nuxt/scripts/commit/13184f8)) +- Sync lock ([28346e2](https://github.com/nuxt/scripts/commit/28346e2)) +- Throw error if nuxt api is down ([d8b79f1](https://github.com/nuxt/scripts/commit/d8b79f1)) +- Bump unhead 1.10.2 ([83d6d18](https://github.com/nuxt/scripts/commit/83d6d18)) +- Unhead 1.10.3 ([af13a30](https://github.com/nuxt/scripts/commit/af13a30)) +- Bump deps ([50126bf](https://github.com/nuxt/scripts/commit/50126bf)) +- Avoid runtime valibot dependency ([2033f16](https://github.com/nuxt/scripts/commit/2033f16)) +- Soft dependency unhead 1.10.4 ([5b61da0](https://github.com/nuxt/scripts/commit/5b61da0)) +- Avoid adding `crossorigin` for same domain scripts ([dd75a31](https://github.com/nuxt/scripts/commit/dd75a31)) +- Fix test ([339162e](https://github.com/nuxt/scripts/commit/339162e)) +- Tests ([260eb52](https://github.com/nuxt/scripts/commit/260eb52)) +- Broken watcher clean up ([a0a8118](https://github.com/nuxt/scripts/commit/a0a8118)) +- **ci:** Skip linting ([12ba618](https://github.com/nuxt/scripts/commit/12ba618)) +- Fix flaky test ([35f0657](https://github.com/nuxt/scripts/commit/35f0657)) + +### ❤️ Contributors + +- Harlan +- Harlan Wilton +- Aman Desai +- Daniel Roe +- Alfarish Fizikri +- Alexander Lichter ## v0.7.1 diff --git a/README.md b/README.md index d429351a..dba0fd64 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ [![nuxt-scripts-social-card](https://github.com/nuxt/scripts/blob/main/.github/banner.png)](https://scripts.nuxt.com) -[![npm version][npm-version-src]][npm-version-href] -[![npm downloads][npm-downloads-src]][npm-downloads-href] +[![npm version][npm-version-src]][npm-href] +[![npm downloads][npm-downloads-src]][npm-href] [![License][license-src]][license-href] [![Nuxt][nuxt-src]][nuxt-href] [![Volta][volta-src]][volta-href] @@ -17,7 +17,7 @@ Better Privacy, Performance, and DX for Third-Party Scripts in Nuxt Apps. ## Features -- 🪨 Built on top of [Unhead](https://unhead.unjs.io/usage/composables/use-script) +- 🪨 Built on top of [Unhead Script Loading](https://unhead.unjs.io/docs/typescript/head/guides/core-concepts/loading-scripts) - 🎁 20+ third-party scripts integrations with fine-grained performance optimizations - 🏎️ Performance: Self hosting, advanced script loading triggers, best-practice defaults. - 🕵️ Privacy: Defaults to protect end users identity, script consent management APIs. @@ -29,7 +29,7 @@ Better Privacy, Performance, and DX for Third-Party Scripts in Nuxt Apps. Loading third-party IIFE scripts using `useHead` composable is easy. However, things start getting more complicated quickly around SSR, lazy loading, and type safety. -Nuxt Scripts was created to solve these issues and more with the goal of making third-party scripts more performant, +Nuxt Scripts solves these issues and more with the goal of making third-party scripts more performant, have better privacy and be better DX overall. ## 🚀 Quick Start @@ -40,7 +40,22 @@ To get started, simply run: npx nuxi@latest module add scripts ``` -That's it! The Nuxt Scripts module should be downloaded and added to your Nuxt Config `modules`. +> [!TIP] +> Generate an Agent Skill for this package using [skilld](https://github.com/harlan-zw/skilld): +> ```bash +> npx skilld add @nuxt/scripts +> ``` + +That's it. The Nuxt Scripts module should be downloaded and added to your Nuxt Config `modules`. + +## 📦 Examples + +Explore live examples on [StackBlitz](https://stackblitz.com): + +- [Cookie Consent](https://stackblitz.com/github/nuxt/scripts/tree/main/examples/cookie-consent) - Google Consent Mode v2 with GTM +- [Granular Consent](https://stackblitz.com/github/nuxt/scripts/tree/main/examples/granular-consent) - Per-category consent management +- [Custom Script](https://stackblitz.com/github/nuxt/scripts/tree/main/examples/custom-script) - Integrating any third-party script +- [Performance](https://stackblitz.com/github/nuxt/scripts/tree/main/examples/performance) - Optimize loading with triggers ## ⛰️ Next Steps @@ -58,15 +73,13 @@ Licensed under the [MIT license](https://github.com/nuxt/scripts/blob/main/LICEN [npm-version-src]: https://img.shields.io/npm/v/@nuxt/scripts/latest.svg?style=flat&colorA=18181B&colorB=28CF8D -[npm-version-href]: https://npmjs.com/package/@nuxt/scripts/v/rc - [npm-downloads-src]: https://img.shields.io/npm/dm/@nuxt/scripts.svg?style=flat&colorA=18181B&colorB=28CF8D -[npm-downloads-href]: https://npmjs.com/package/@nuxt/scripts/v/rc +[npm-href]: https://npmjs.com/package/@nuxt/scripts [license-src]: https://img.shields.io/npm/l/@nuxt/scripts.svg?style=flat&colorA=18181B&colorB=28CF8D -[license-href]: https://npmjs.com/package/@nuxt/scripts/v/rc +[license-href]: https://github.com/nuxt/scripts/blob/main/LICENSE.md -[nuxt-src]: https://img.shields.io/badge/Nuxt-18181B?logo=nuxt.js +[nuxt-src]: https://img.shields.io/badge/Nuxt-18181B?logo=nuxt [nuxt-href]: https://nuxt.com [volta-src]: https://user-images.githubusercontent.com/904724/209143798-32345f6c-3cf8-4e06-9659-f4ace4a6acde.svg diff --git a/build.config.ts b/build.config.ts index 62955942..6fb80700 100644 --- a/build.config.ts +++ b/build.config.ts @@ -1,7 +1,37 @@ import { defineBuildConfig } from 'unbuild' export default defineBuildConfig({ + declaration: true, entries: [ './src/registry', + './src/stats', + './src/types-source', + ], + alias: { + '#nuxt-scripts-validator': 'valibot', + }, + externals: [ + 'nuxt', + 'nuxt/schema', + '@nuxt/kit', + '@nuxt/schema', + 'nitropack', + 'nitropack/types', + 'h3', + 'vue', + 'vue-router', + '@vue/runtime-core', + '#imports', + '@unhead/vue', + '@unhead/schema', + 'knitwork', + '@vimeo/player', + 'esbuild', + 'unimport', + '#nuxt-scripts/types', + '#nuxt-scripts/utils', + 'posthog-js', + '#build/modules/nuxt-scripts-gtm', + '#build/modules/nuxt-scripts-ga', ], }) diff --git a/client/app.vue b/client/app.vue index 06805126..71e0b139 100644 --- a/client/app.vue +++ b/client/app.vue @@ -1,40 +1,163 @@ diff --git a/client/components/ScriptLoadTime.vue b/client/components/ScriptLoadTime.vue new file mode 100644 index 00000000..3e88ebbc --- /dev/null +++ b/client/components/ScriptLoadTime.vue @@ -0,0 +1,15 @@ + + + diff --git a/client/components/ScriptSize.vue b/client/components/ScriptSize.vue new file mode 100644 index 00000000..225639fb --- /dev/null +++ b/client/components/ScriptSize.vue @@ -0,0 +1,15 @@ + + + diff --git a/client/components/ScriptStatus.vue b/client/components/ScriptStatus.vue new file mode 100644 index 00000000..5bad4c96 --- /dev/null +++ b/client/components/ScriptStatus.vue @@ -0,0 +1,44 @@ + + + diff --git a/client/composables/shiki.ts b/client/composables/shiki.ts index 61c7fb5b..42a62d24 100644 --- a/client/composables/shiki.ts +++ b/client/composables/shiki.ts @@ -1,34 +1,30 @@ -import type { Highlighter, Lang } from 'shiki' -import { getHighlighter } from 'shiki' +import type { HighlighterCore } from 'shiki' +import { createHighlighterCore } from 'shiki/core' +import { createJavaScriptRegexEngine } from 'shiki/engine/javascript' import { computed, ref, toValue } from 'vue' import type { MaybeRef } from '@vueuse/core' import { devtools } from './rpc' -export const shiki = ref() +export const shiki = ref() export function loadShiki() { // Only loading when needed - return getHighlighter({ + return createHighlighterCore({ themes: [ - 'vitesse-dark', - 'vitesse-light', + import('@shikijs/themes/vitesse-light'), + import('@shikijs/themes/vitesse-dark'), ], langs: [ - 'css', - 'javascript', - 'typescript', - 'html', - 'vue', - 'vue-html', - 'bash', - 'diff', + import('@shikijs/langs/json'), + import('@shikijs/langs/typescript'), ], + engine: createJavaScriptRegexEngine(), }).then((i) => { shiki.value = i }) } -export function renderCodeHighlight(code: MaybeRef, lang?: Lang) { +export function renderCodeHighlight(code: MaybeRef, lang: 'json' | 'typescript') { return computed(() => { const colorMode = devtools.value?.colorMode || 'light' return shiki.value!.codeToHtml(toValue(code), { diff --git a/client/nuxt.config.ts b/client/nuxt.config.ts index 25008f04..02ff7792 100644 --- a/client/nuxt.config.ts +++ b/client/nuxt.config.ts @@ -4,20 +4,20 @@ import { DEVTOOLS_UI_ROUTE } from '../src/constants' const resolver = createResolver(import.meta.url) export default defineNuxtConfig({ - ssr: false, - devtools: { enabled: false }, modules: [ '@nuxt/devtools-ui-kit', ], - nitro: { - output: { - publicDir: resolver.resolve('../dist/client'), - }, - }, + ssr: false, + devtools: { enabled: false }, app: { baseURL: DEVTOOLS_UI_ROUTE, }, compatibilityDate: '2024-07-04', + nitro: { + output: { + publicDir: resolver.resolve('../dist/client'), + }, + }, }) diff --git a/client/package.json b/client/package.json index cabb8a65..5035900a 100644 --- a/client/package.json +++ b/client/package.json @@ -11,12 +11,12 @@ "generate": "nuxi generate" }, "devDependencies": { - "@iconify-json/carbon": "^1.2.1", - "@nuxt/devtools-kit": "^1.4.1", - "@nuxt/devtools-ui-kit": "latest", - "@nuxt/kit": "^3.13.0", - "nuxt": "latest", - "vue": "3.4.31", + "@iconify-json/carbon": "^1.2.19", + "@nuxt/devtools-kit": "^3.2.3", + "@nuxt/devtools-ui-kit": "^3.2.3", + "@nuxt/kit": "^4.4.2", + "nuxt": "^4.4.2", + "vue": "^3.5.30", "vue-router": "latest" } } diff --git a/client/utils/fetch.ts b/client/utils/fetch.ts index 5b9f401c..9cabdb50 100644 --- a/client/utils/fetch.ts +++ b/client/utils/fetch.ts @@ -1,24 +1,48 @@ -export async function getScriptSize(url: string) { - const compressedResponse = await fetch(url, { headers: { 'Accept-Encoding': 'gzip' } }) - return bytesToSize(await getResponseSize(compressedResponse)) +export async function fetchScript(url: string) { + const compressedResponse = await fetch(url, { headers: { 'Accept-Encoding': 'gzip' } }).catch((err) => { + return { + size: null, + error: err, + } + }) + if (compressedResponse?.error) { + return compressedResponse as { size: null, error: Error } + } + if (!compressedResponse.ok) { + return { + size: null, + error: new Error(`Failed to fetch ${compressedResponse.status} ${compressedResponse.statusText}`), + } + } + const size = await getResponseSize(compressedResponse) + if (!size) { + return { + size: null, + } + } + return { + size: bytesToSize(size), + } } -async function getResponseSize(response) { - const reader = response.body.getReader() - const contentLength = +response.headers.get('Content-Length') +async function getResponseSize(response: Response) { + const reader = response.body?.getReader() + const contentLength = response.headers.get('Content-Length') if (contentLength) { - return contentLength + return Number(contentLength) } - else { - let total = 0 - while (true) { - const { done, value } = await reader.read() - if (done) - return total - total += value.length - } + if (!reader) { + return null + } + let total = 0 + let done = false + while (!done) { + const data = await reader.read() + done = data.done + total += data.value?.length || 0 } + return total > 0 ? total : null } function bytesToSize(bytes: number) { diff --git a/docs/.env.example b/docs/.env.example deleted file mode 100644 index 8f0079ca..00000000 --- a/docs/.env.example +++ /dev/null @@ -1,5 +0,0 @@ -# Production license for @nuxt/ui-pro, get one at https://ui.nuxt.com/pro/purchase -NUXT_UI_PRO_LICENSE= - -# Public URL, used for OG Image when running nuxt generate -NUXT_PUBLIC_SITE_URL= diff --git a/docs/.npmrc b/docs/.npmrc deleted file mode 100644 index bf2e7648..00000000 --- a/docs/.npmrc +++ /dev/null @@ -1 +0,0 @@ -shamefully-hoist=true diff --git a/docs/.nuxtrc b/docs/.nuxtrc deleted file mode 100644 index 3c8c6a11..00000000 --- a/docs/.nuxtrc +++ /dev/null @@ -1 +0,0 @@ -imports.autoImport=true \ No newline at end of file diff --git a/docs/app.config.ts b/docs/app.config.ts deleted file mode 100644 index f1802cc5..00000000 --- a/docs/app.config.ts +++ /dev/null @@ -1,38 +0,0 @@ -export default defineAppConfig({ - ui: { - primary: 'green', - gray: 'slate', - footer: { - bottom: { - left: 'text-sm text-gray-500 dark:text-gray-400', - wrapper: 'border-t border-gray-200 dark:border-gray-800', - }, - }, - variables: { - dark: { - background: 'var(--color-gray-950)', - }, - }, - }, - header: { - links: [{ - 'icon': 'i-simple-icons-github', - 'to': 'https://github.com/nuxt/scripts', - 'target': '_blank', - 'aria-label': 'Nuxt Scripts', - }], - }, - toc: { - title: 'Table of Contents', - bottom: { - title: 'Community', - edit: 'https://github.com/nuxt/scripts/edit/main/docs/content', - links: [{ - icon: 'i-heroicons-star', - label: 'Star on GitHub', - to: 'https://github.com/nuxt/scripts', - target: '_blank', - }], - }, - }, -}) diff --git a/docs/app.vue b/docs/app.vue deleted file mode 100644 index 2d7aef21..00000000 --- a/docs/app.vue +++ /dev/null @@ -1,43 +0,0 @@ - - - - - diff --git a/docs/components/Footer.vue b/docs/components/Footer.vue deleted file mode 100644 index e9a8a011..00000000 --- a/docs/components/Footer.vue +++ /dev/null @@ -1,84 +0,0 @@ - - - diff --git a/docs/components/Header.vue b/docs/components/Header.vue deleted file mode 100644 index 7cd9603b..00000000 --- a/docs/components/Header.vue +++ /dev/null @@ -1,48 +0,0 @@ - - - diff --git a/docs/components/Logo.vue b/docs/components/Logo.vue deleted file mode 100644 index 27954e1f..00000000 --- a/docs/components/Logo.vue +++ /dev/null @@ -1,16 +0,0 @@ - diff --git a/docs/components/LogoScroller.vue b/docs/components/LogoScroller.vue deleted file mode 100644 index 9130f707..00000000 --- a/docs/components/LogoScroller.vue +++ /dev/null @@ -1,31 +0,0 @@ - - - diff --git a/docs/components/OgImage/Docs.vue b/docs/components/OgImage/Docs.vue deleted file mode 100644 index 6ac36186..00000000 --- a/docs/components/OgImage/Docs.vue +++ /dev/null @@ -1,61 +0,0 @@ - - - diff --git a/docs/components/OgImage/Home.vue b/docs/components/OgImage/Home.vue deleted file mode 100644 index 6ac36186..00000000 --- a/docs/components/OgImage/Home.vue +++ /dev/null @@ -1,61 +0,0 @@ - - - diff --git a/docs/components/ShowcaseCard.vue b/docs/components/ShowcaseCard.vue deleted file mode 100644 index a6e69ddd..00000000 --- a/docs/components/ShowcaseCard.vue +++ /dev/null @@ -1,33 +0,0 @@ - - - diff --git a/docs/components/UInputCopy.vue b/docs/components/UInputCopy.vue deleted file mode 100644 index fab4ffd4..00000000 --- a/docs/components/UInputCopy.vue +++ /dev/null @@ -1,34 +0,0 @@ - - - diff --git a/docs/components/content/Alert.vue b/docs/components/content/Alert.vue deleted file mode 100644 index 3f9b9e0c..00000000 --- a/docs/components/content/Alert.vue +++ /dev/null @@ -1,18 +0,0 @@ - - - diff --git a/docs/components/content/Caution.vue b/docs/components/content/Caution.vue deleted file mode 100644 index c584a12a..00000000 --- a/docs/components/content/Caution.vue +++ /dev/null @@ -1,22 +0,0 @@ - - - diff --git a/docs/components/content/CrispDemo.vue b/docs/components/content/CrispDemo.vue deleted file mode 100644 index 0cc826ea..00000000 --- a/docs/components/content/CrispDemo.vue +++ /dev/null @@ -1,54 +0,0 @@ - - - - - diff --git a/docs/components/content/GoogleMapsDemo.vue b/docs/components/content/GoogleMapsDemo.vue deleted file mode 100644 index 30b52424..00000000 --- a/docs/components/content/GoogleMapsDemo.vue +++ /dev/null @@ -1,70 +0,0 @@ - - - diff --git a/docs/components/content/Important.vue b/docs/components/content/Important.vue deleted file mode 100644 index 5b13cefb..00000000 --- a/docs/components/content/Important.vue +++ /dev/null @@ -1,22 +0,0 @@ - - - diff --git a/docs/components/content/IntercomDemo.vue b/docs/components/content/IntercomDemo.vue deleted file mode 100644 index 54fdb288..00000000 --- a/docs/components/content/IntercomDemo.vue +++ /dev/null @@ -1,32 +0,0 @@ - - - - - diff --git a/docs/components/content/LemonSqueezyDemo.vue b/docs/components/content/LemonSqueezyDemo.vue deleted file mode 100644 index a2be29df..00000000 --- a/docs/components/content/LemonSqueezyDemo.vue +++ /dev/null @@ -1,32 +0,0 @@ - - - diff --git a/docs/components/content/Note.vue b/docs/components/content/Note.vue deleted file mode 100644 index 06e00b4a..00000000 --- a/docs/components/content/Note.vue +++ /dev/null @@ -1,22 +0,0 @@ - - - diff --git a/docs/components/content/StripeDemo.vue b/docs/components/content/StripeDemo.vue deleted file mode 100644 index 6324f30d..00000000 --- a/docs/components/content/StripeDemo.vue +++ /dev/null @@ -1,11 +0,0 @@ - diff --git a/docs/components/content/Tip.vue b/docs/components/content/Tip.vue deleted file mode 100644 index 916b338a..00000000 --- a/docs/components/content/Tip.vue +++ /dev/null @@ -1,22 +0,0 @@ - - - diff --git a/docs/components/content/VimeoDemo.vue b/docs/components/content/VimeoDemo.vue deleted file mode 100644 index b9f3262a..00000000 --- a/docs/components/content/VimeoDemo.vue +++ /dev/null @@ -1,28 +0,0 @@ - - - diff --git a/docs/components/content/Warning.vue b/docs/components/content/Warning.vue deleted file mode 100644 index bd53e066..00000000 --- a/docs/components/content/Warning.vue +++ /dev/null @@ -1,22 +0,0 @@ - - - diff --git a/docs/components/content/YoutubeDemo.vue b/docs/components/content/YoutubeDemo.vue deleted file mode 100644 index 457d09f5..00000000 --- a/docs/components/content/YoutubeDemo.vue +++ /dev/null @@ -1,31 +0,0 @@ - - - diff --git a/docs/composables/useScriptsRegistry.ts b/docs/composables/useScriptsRegistry.ts deleted file mode 100644 index 116152a2..00000000 --- a/docs/composables/useScriptsRegistry.ts +++ /dev/null @@ -1,5 +0,0 @@ -import { registry } from '../../src/registry' - -export function useScriptsRegistry() { - return registry() // we don't need paths here -} diff --git a/docs/content/_cookie-api.md b/docs/content/_cookie-api.md deleted file mode 100644 index 7db1d329..00000000 --- a/docs/content/_cookie-api.md +++ /dev/null @@ -1,11 +0,0 @@ -```vue - - -``` diff --git a/docs/content/_magic-api.md b/docs/content/_magic-api.md deleted file mode 100644 index 0590ae9e..00000000 --- a/docs/content/_magic-api.md +++ /dev/null @@ -1,14 +0,0 @@ -```vue - -``` diff --git a/docs/content/docs/1.getting-started/_dir.yml b/docs/content/docs/1.getting-started/.navigation.yml similarity index 100% rename from docs/content/docs/1.getting-started/_dir.yml rename to docs/content/docs/1.getting-started/.navigation.yml diff --git a/docs/content/docs/1.getting-started/1.index.md b/docs/content/docs/1.getting-started/1.index.md index c4952080..ca1a04f0 100644 --- a/docs/content/docs/1.getting-started/1.index.md +++ b/docs/content/docs/1.getting-started/1.index.md @@ -1,6 +1,8 @@ --- + title: Introduction description: Nuxt Scripts is Nuxt DX for third-party scripts. + --- Nuxt Scripts enhances the performance, privacy, and developer experience (DX) when incorporating third-party scripts into Nuxt applications. @@ -22,13 +24,13 @@ Using the `useHead` composable to load third-party IIFE scripts is straightforwa Third-party resources like analytics tools, video embeds, maps, and social media integrations enhance website functionality but aren't directly managed by site owners. A single resource may have a minimal performance impact, but multiple resources can significantly degrade user experience. Scripts, in particular, can delay interactivity and obstruct page rendering. -According to the Chrome User Experience Report, Nuxt sites with numerous third-party resources typically show lower [Interaction to Next Paint (INP)](https://web.dev/articles/inp) and [Largest Contentful Paint (LCP)](https://web.dev/articles/lcp) scores. Despite the correlation not proving causation, lab tests and the [Web Almanac](https://almanac.httparchive.org/en/2022/third-parties) confirm significant performance impacts from third-party resources. +According to the Chrome User Experience Report, Nuxt sites with numerous third-party resources typically show lower [Interaction to Next Paint (INP)](https://web.dev/articles/inp) and [Largest Contentful Paint (LCP)](https://web.dev/articles/lcp) scores. Despite the correlation not proving causation, lab tests and the [2022 Web Almanac chapter on Third Parties](https://almanac.httparchive.org/en/2022/third-parties) confirm significant performance impacts from third-party resources. ## Nuxt Script Features ### 🏎️ Performance -- Script loading is triggered only when Nuxt is ready, by default. +- Nuxt triggers script loading only when ready, by default. - More advanced triggering of script loads, independent of implementation specifics. - Improved script loading times with [Bundling Remote Scripts](/docs/guides/bundling). diff --git a/docs/content/docs/1.getting-started/2.installation.md b/docs/content/docs/1.getting-started/2.installation.md index 564d8916..a4088f5f 100644 --- a/docs/content/docs/1.getting-started/2.installation.md +++ b/docs/content/docs/1.getting-started/2.installation.md @@ -1,6 +1,8 @@ --- + title: Installation description: Learn how to create a Nuxt Scripts project or add it to your current Nuxt project. + --- ## Quick Start @@ -8,10 +10,16 @@ description: Learn how to create a Nuxt Scripts project or add it to your curren To get started, simply run: ```bash -npx nuxi@latest module add @nuxt/scripts +npx nuxi@latest module add scripts@beta ``` -That's it! The Nuxt Scripts module should be downloaded and added to your Nuxt Config `modules`. +> [!TIP] +> Generate an Agent Skill for this package using [skilld](https://github.com/harlan-zw/skilld): +> ```bash +> npx skilld add @nuxt/scripts +> ``` + +That's it. The Nuxt Scripts module should be downloaded and added to your Nuxt Config `modules`. ## Next Steps @@ -20,5 +28,5 @@ Need some inspiration to start using Nuxt Scripts? Try out the following: 1. 🎉 Make it rain emojis with the [Confetti Tutorial](/docs/getting-started/confetti-tutorial). 2. 📚 Learn about how the [Script Loading](/docs/guides/script-triggers) works. 3. 🔍 Explore the [Script Registry](/scripts) for popular pre-configured third-party scripts. -3. 🚀 Load other scripts with [useScript](/docs/api/use-script) or [Global Scripts](/docs/guides/global). +3. 🚀 Load other scripts with [`useScript()`{lang="ts"}](/docs/api/use-script){lang="ts"} or [Global Scripts](/docs/guides/global). 4. 🔨 Fine-tune your performance and privacy with [Bundling](/docs/guides/bundling) and [Consent Management](/docs/guides/consent). diff --git a/docs/content/docs/1.getting-started/3.confetti-tutorial.md b/docs/content/docs/1.getting-started/3.confetti-tutorial.md index c8936a0f..27a32f63 100644 --- a/docs/content/docs/1.getting-started/3.confetti-tutorial.md +++ b/docs/content/docs/1.getting-started/3.confetti-tutorial.md @@ -1,6 +1,8 @@ --- + title: "Tutorial: Load js-confetti" description: "Learn how to load the js-confetti script using the Nuxt Scripts module." + --- ## Introduction @@ -8,24 +10,24 @@ description: "Learn how to load the js-confetti script using the Nuxt Scripts mo In this tutorial, you will learn how to load the [js-confetti](https://github.com/loonywizard/js-confetti) script using the Nuxt Scripts module. You'll learn about the following: -- What the `useScriptNpm` registry script is. +- What the [`useScriptNpm()`{lang="ts"}](/scripts/npm){lang="ts"} registry script is. - How to load the `js-confetti` script using it. - Adding types to loaded scripts. - Using [proxied functions](/docs/guides/key-concepts#understanding-proxied-functions) to call the script. -## Background on useScriptNpm +## Background on [`useScriptNpm()`{lang="ts"}](/scripts/npm){lang="ts"} -To load the script, we'll be using the [useScriptNpm](/scripts/utility/npm). +To load the script, we'll be using the [`useScriptNpm()`{lang="ts"}](/scripts/npm){lang="ts"}. This is a [registry script](/scripts), a supported third-party integration built on top of the -[useScript](/docs/api/use-script) composable that allows you to load scripts from NPM. +[`useScript()`{lang="ts"}](/docs/api/use-script){lang="ts"} composable that allows you to load scripts from [npm](https://npmjs.com). -When working with NPM files, you'd typically include them as a node_module dependency in the `package.json` file. However, +When working with npm files, you'd typically include them as a `node_module` dependency in the `package.json` file. However, optimizing the script loading of these scripts can be difficult, requiring a dynamic import of the module from a separate chunk and loading it only when needed. It also slows down your build as the module needs to be transpiled. -The `useScriptNpm` registry script abstracts this process, allowing you to load scripts that have been exported as immediately invokable functions, +The [`useScriptNpm()`{lang="ts"}](/scripts/npm){lang="ts"} registry script abstracts this process, allowing you to load scripts that export immediately invokable functions, with a single line of code . In many instances it will still make more sense to include the script as a dependency in the `package.json` file, but for scripts that are not used often or @@ -50,7 +52,7 @@ useScript('https://cdn.jsdelivr.net/npm/js-confetti@0.12.0/dist/js-confetti.brow ```ts [useHead] useHead({ - scripts: [ + script: [ { src: 'https://cdn.jsdelivr.net/npm/js-confetti@latest/dist/js-confetti.browser.js' } ] }) @@ -60,7 +62,7 @@ useHead({ ### Loading the script -Within your one of your components, you'll want to load the script. You can do this by using the `useScriptNpm` registry script. +Within one of your components, you'll want to load the script. You can do this by using the [`useScriptNpm()`{lang="ts"}](/scripts/npm){lang="ts"} registry script. ```vue [app.vue] ``` -If you check your browser requests, you should see the script being loaded. +If you check your browser requests, you should see the script load. ### Resolving the third-party script API -Now that the script is loaded, you can use it in your component. To do so we need to tell the underlying API how to use the script, for this we can -leverage the [use](/docs/api/use-script#use) function. +Now that the script loads, you can use it in your component. To do so we need to tell the basic API how to use the script, for this we can +Use the [use](/docs/api/use-script#use) function. -This function is only called on the client-side and is used to resolve the third-party script. +This function runs only on the client-side to resolve the third-party script. ```vue [app.vue] ``` -### Bonus: Event based script loading +### Bonus: Trigger-based script loading You can delay the loading of the script by using the `trigger` option. This can be useful if you want to load the script after a certain event or time. -In this example we'll combine the [useScriptTriggerElement](/docs/api/use-script-trigger-element) composable with the `useScriptNpm` composable to load the script after a certain element is in view. +See the [Script Triggers](/docs/guides/script-triggers) guide for all available options. + +#### Using a Ref + +The simplest approach is to use a `ref` - the script loads when the ref becomes truthy. + +```vue [app.vue] + + + +``` + +::tip +You can also use a computed ref or getter function: `trigger: computed(() => someCondition.value)`{lang="ts"} or `trigger: () => shouldLoad.value`. +:: + +#### Using Element Events + +You can also use the [`useScriptTriggerElement()`{lang="ts"}](/docs/api/use-script-trigger-element){lang="ts"} composable to trigger loading based on element interactions. ```vue [app.vue] @@ -214,3 +223,103 @@ export function useFathomAnalytics() { }) } ``` + +## Extending the Script Registry + +You can extend the script registry using the `scripts:registry` hook in your `nuxt.config.ts`: + +```ts [nuxt.config.ts] +import { createResolver } from '@nuxt/kit' + +const { resolve } = createResolver(import.meta.url) + +export default defineNuxtConfig({ + modules: ['@nuxt/scripts'], + + hooks: { + 'scripts:registry': function (registry) { + registry.push({ + category: 'custom', + label: 'My Custom Analytics', + logo: '...', // optional + import: { + name: 'useScriptMyAnalytics', + from: resolve('./composables/useScriptMyAnalytics'), + }, + }) + }, + }, + + devtools: { + enabled: true, + }, +}) +``` + +Then create your custom script composable: + +```ts [composables/useScriptMyAnalytics.ts] +import { object, string } from '#nuxt-scripts-validator' +import { useRegistryScript } from '#nuxt-scripts/utils' + +export interface MyAnalyticsApi { + track: (event: string, data?: Record) => void + identify: (userId: string) => void +} + +// Schema for validation and DevTools metadata +const MyAnalyticsSchema = object({ + apiKey: string(), +}) + +export function useScriptMyAnalytics(options?: { + apiKey: string + scriptOptions?: NuxtUseScriptOptions +}) { + return useRegistryScript('myAnalytics', () => ({ + scriptInput: { + src: 'https://analytics.example.com/sdk.js', + }, + scriptOptions: { + ...options?.scriptOptions, + use() { + // Initialize your script + window.MyAnalytics.init(options?.apiKey) + return window.MyAnalytics as T + }, + }, + }), options, { schema: MyAnalyticsSchema }) +} +``` + +### Using Custom Registry Scripts + +Once registered, your custom scripts work exactly like built-in registry scripts: + +```vue [pages/index.vue] + + + +``` + +### DevTools Integration + +When you include a validation schema, Nuxt Scripts automatically populates DevTools metadata with your script's configuration. The `registryKey` and `registryMeta` are automatically set in development mode, allowing you to: diff --git a/docs/content/docs/1.guides/1.script-triggers.md b/docs/content/docs/1.guides/1.script-triggers.md index 9655c4a7..fc18a0ad 100644 --- a/docs/content/docs/1.guides/1.script-triggers.md +++ b/docs/content/docs/1.guides/1.script-triggers.md @@ -1,37 +1,121 @@ --- -title: Triggering Script Loading + +title: Script Triggers +description: Control when scripts load with Nuxt Scripts' flexible trigger system. + --- -Nuxt Scripts provides several ways to trigger the loading of scripts. +::callout{icon="i-heroicons-play" to="https://stackblitz.com/github/nuxt/scripts/tree/main/examples/performance" target="_blank"} +Try the live [Performance Example](https://stackblitz.com/github/nuxt/scripts/tree/main/examples/performance) on [StackBlitz](https://stackblitz.com) to see triggers in action. +:: + +Nuxt Scripts provides a flexible trigger system to control when scripts load, helping you optimize performance by loading scripts at the right moment for your users. + +## How Triggers Work + +Pass any reactive value as `trigger` - the script loads when it becomes truthy: + +```ts +const shouldLoad = ref(false) + +useScript('https://example.com/script.js', { + trigger: shouldLoad +}) + +// Later: trigger loading +shouldLoad.value = true +``` + +This works with refs, computed refs, getter functions, and promises: + +```ts +// Ref +trigger: shouldLoad + +// Computed +trigger: computed(() => !!route.query.affiliateId) + +// Getter function +trigger: () => shouldLoad.value + +// Promise +trigger: new Promise(resolve => setTimeout(resolve, 3000)) +``` + +## Default: onNuxtReady + +By default, scripts use the `onNuxtReady` trigger, providing "idle loading" behavior where scripts load only after the page is fully interactive. This minimizes impact on Core Web Vitals and user experience. + +The `onNuxtReady` trigger ensures scripts load: +- After Nuxt hydration is complete +- When the browser is idle and the main thread is available +- Without blocking critical page rendering or user interactions + +```ts +// Default behavior - explicit for clarity +useScript('https://widget.intercom.io/widget/abc123', { + trigger: 'onNuxtReady' +}) + +// Registry scripts also use onNuxtReady by default +useScriptGoogleAnalytics({ + id: 'GA_MEASUREMENT_ID' + // trigger: 'onNuxtReady' is implied +}) +``` + +You can change this default by modifying the [defaultScriptOptions](/docs/api/nuxt-config#defaultscriptoptions). + +## Specialized Triggers + +### Idle Timeout + +Use [`useScriptTriggerIdleTimeout()`{lang="ts"}](/docs/api/use-script-trigger-idle-timeout){lang="ts"} to delay script loading for a specified time after Nuxt is ready: ::code-group -```ts [useScript] -useScript({ - src: 'https://example.com/script.js', -}, { - // load however you like! - trigger: new Promise(resolve => setTimeout(resolve, 3000)) +```ts [Composable] +useScript('https://example.com/analytics.js', { + trigger: useScriptTriggerIdleTimeout({ timeout: 5000 }) }) ``` -```ts [Registry Script] -useScriptMetaPixel({ - id: '1234567890', - scriptOptions: { - // load however you like! - trigger: new Promise(resolve => setTimeout(resolve, 3000)) +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + scripts: { + registry: { + googleAnalytics: [{ + id: 'GA_MEASUREMENT_ID' + }, { + trigger: { idleTimeout: 3000 } + }] + } } }) ``` -```ts [Global Script] +:: + +### User Interaction + +Use [`useScriptTriggerInteraction()`{lang="ts"}](/docs/api/use-script-trigger-interaction){lang="ts"} to load scripts when users interact with your site: + +::code-group + +```ts [Composable] +useScript('https://example.com/chat-widget.js', { + trigger: useScriptTriggerInteraction({ + events: ['scroll', 'click', 'keydown'] + }) +}) +``` + +```ts [nuxt.config.ts] export default defineNuxtConfig({ scripts: { globals: { - myScript: ['https://example.com/script.js', { - // load however you like! - trigger: 'onNuxtReady' + chatWidget: ['https://widget.example.com/chat.js', { + trigger: { interaction: ['scroll', 'click', 'touchstart'] } }] } } @@ -40,50 +124,32 @@ export default defineNuxtConfig({ :: -## Default Behavior - -By default, scripts are loaded when Nuxt is fully hydrated. You can change this default by modifying the [defaultScriptOptions](/docs/api/nuxt-config#defaultscriptoptions). - -## Element Event Triggers +### Element Events -The [useScriptTriggerElement](/docs/api/use-script-trigger-element) composable allows you to hook into element events as a way to load script. This is useful for loading scripts when a user interacts with a specific element. +Use [`useScriptTriggerElement()`{lang="ts"}](/docs/api/use-script-trigger-element){lang="ts"} to trigger scripts based on specific element interactions: ```ts -const somethingEl = ref() -const { trigger } = useScript({ - src: 'https://example.com/script.js', -}, { +const buttonEl = ref() + +useScript('https://example.com/feature.js', { trigger: useScriptTriggerElement({ - trigger: 'hover', - somethingEl, + trigger: 'visible', // or 'hover', 'click', etc. + el: buttonEl, }) }) ``` -It has support for the following triggers: -- `visible` - Triggered when the element becomes visible in the viewport. -- `mouseover` - Triggered when the element is hovered over. +## Basic Triggers -## Manual Trigger +### Manual Control -The `manual` trigger allows you to manually trigger the loading of a script. This gives you complete -control over when the script is loaded. +Use `manual` trigger for complete control over script loading: ```ts const { load } = useScript('https://example.com/script.js', { trigger: 'manual' }) -// ... -load() -``` - -## Promise -You can use a promise to trigger the loading of a script. This is useful for any other custom trigger you might want to use. - -```ts -const myScript = useScript('/script.js', { - // load after 3 seconds - trigger: new Promise(resolve => setTimeout(resolve, 3000)) -}) +// Load when you decide +await load() ``` diff --git a/docs/content/docs/1.guides/1.warmup.md b/docs/content/docs/1.guides/1.warmup.md new file mode 100644 index 00000000..d50e9189 --- /dev/null +++ b/docs/content/docs/1.guides/1.warmup.md @@ -0,0 +1,58 @@ +--- + +title: Warmup Strategy +description: Customize the preload or preconnect strategy used for your scripts. + +--- + +## Background + +Nuxt Scripts will insert relevant warmup `link` tags to optimize the loading of your scripts. Optimizing +for the quickest load after Nuxt has finished hydrating. + +For example if we have a script like so: + +```ts +useScript('/script.js') +``` + +This code will load in `/script.js` on the `onNuxtReady` event. As the network may be idle while your Nuxt App is hydrating, +Nuxt Scripts will use this time to warmup the script by inserting a `preload` tag in the `head` of the document. + +```html + +``` + +This behavior only applies when using the `client` or `onNuxtReady` [Script Triggers](/docs/guides/script-triggers). +To customize the behavior further, you can use the `warmupStrategy` option. + +## `warmupStrategy` + +You can use the `warmupStrategy` option to customize the `link` tag inserted for the script. The option can be a function +that returns an object with the following properties: + +* - `false` - Disable warmup. +* - `'preload'` - Preload the script, use when you load the script immediately. +* - `'preconnect'` or `'dns-prefetch'` - Preconnect to the script origin, use when you know you will load a script within 10 seconds. Only works when loading a script from a different origin, will fallback to `false` if the origin is the same. + +All of these options can also be passed to a callback function, which can be useful when you have a dynamic trigger for the script. + +## `warmup` + +You can call the `warmup` function explicitly to add preconnect or preload link tags for a script. This only works the first time you call the function. + +This can be useful when you know that you are going to load the script shortly. + +```ts +const script = useScript('/video.js', { + trigger: 'manual' +}) +// warmup the script when we think the user may need the script +onVisible(videoContainer, () => { + script.warmup('preload') +}) +// load it in +onClick(videoContainer, () => { + script.load() +}) +``` diff --git a/docs/content/docs/1.guides/2.bundling.md b/docs/content/docs/1.guides/2.bundling.md deleted file mode 100644 index 154f032a..00000000 --- a/docs/content/docs/1.guides/2.bundling.md +++ /dev/null @@ -1,120 +0,0 @@ ---- -title: Bundling Remote Scripts -description: Optimize third-party scripts by bundling them with your app. ---- - -## Background - -When you use scripts from other sites on your website, you rely on another server to load these scripts. This can slow down your site and raise concerns about safety and privacy. - -### Common problems - -- Slower website because it takes time to connect to other servers. -- Safety risks if the other server is hacked. -- Your visitors' data being used inappropriately by other servers. -- Ad blockers or privacy tools might stop these scripts from working. - -### How to fix it - -By bundling these scripts, you can host them yourself, which helps avoid these issues and keeps your site running smoothly. - -## How it Works - -During the build process, your code is checked to find any instances of `useScript` that need to be bundled. - -When a script is identified for bundling, it's downloaded and saved as a public asset at `/_scripts/[hash].js`. Here, `[hash]` represents the hash of the script's URL. - -**Important points about bundling:** - -1. You need to have static values for your script URLs and bundling settings. - -::code-group - -```ts [Input - Pre Build] -// GOOD - Static values allow for bundling -useScript('https://example.com/script.js', { - bundle: true -}) -// BAD - Dynamic values prevent bundling -useScript(scriptSrc, { - bundle: canBundle -}) -``` - -```ts [Output - Post Build] -// GOOD - Script is bundled -useScript('/_scripts/[hash].js', {}) -// BAD - Script is not bundled (remains the same) -useScript(scriptSrc, { - bundle: canBundle -}) -``` - -:: - -2. If the original script changes without a URL change, the bundled version won't update in the browser cache. To handle this, use a versioned URL or a cache-busting query parameter. - -## Usage - -Scripts can be bundled individually or on a global scale using specific settings. - -### Script Options - -To decide if an individual script should be bundled, use the `bundle` option. - -::code-group - -```ts [useScript] -// Opt-in to bundle this specific script -useScript('https://example.com/script.js', { - bundle: true, -}) -``` - -```ts [Registry Script] -// Registry script must support bundling -useScriptGoogleAnalytics('https://example.com/script.js', { - bundle: true, -}) -``` -:: - -### Global Bundling - -Adjust the default behavior for all scripts using the Nuxt Config. This example sets all scripts to be bundled by default. - -```ts [nuxt.config.ts] -export default defineNuxtConfig({ - scripts: { - defaultScriptOptions: { - bundle: true, - } - } -}) -``` - -### Limitations of Bundling - -While many scripts can be bundled, there are exceptions you need to be aware of. - -For instance, certain scripts: -- Require tracking all user interactions for security reasons, like fraud detection (e.g., Stripe). -- Must be served directly from their original source to function properly (e.g., Fathom Analytics). - -Scripts from known registries are pre-configured to either allow or disallow bundling. For your own scripts, you'll need to decide whether bundling is appropriate on a case-by-case basis. - -### Change Asset Behavior - -Use the `assets` option in your configuration to customize how scripts are bundled, such as changing the output directory for the bundled scripts. - -```ts [nuxt.config.ts] -export default defineNuxtConfig({ - scripts: { - assets: { - prefix: '/_custom-script-path/', - } - } -}) -``` - -More configuration options will be available in future updates. diff --git a/docs/content/docs/1.guides/2.first-party.md b/docs/content/docs/1.guides/2.first-party.md new file mode 100644 index 00000000..120f58b8 --- /dev/null +++ b/docs/content/docs/1.guides/2.first-party.md @@ -0,0 +1,702 @@ +--- + +title: First-Party Mode +description: Route third-party script traffic through your domain for improved privacy and reliability. + +--- + +## Background + +When third-party scripts load directly from external servers, they expose your users' data: + +- **IP address exposure** - Every request reveals your users' IP addresses to third parties +- **Third-party cookies** - External scripts can set cookies for cross-site tracking +- **Ad blocker interference** - Privacy tools block requests to known tracking domains +- **Connection overhead** - Extra DNS lookups and TLS handshakes slow page loads + +### How First-Party Mode Helps + +First-party mode routes all script traffic through your domain: + +- **User IPs anonymized** - IPs are anonymized to subnet level before forwarding; third parties can't identify individual users +- **Device fingerprinting reduced** - Screen resolution, User-Agent, and hardware info are generalized to common buckets +- **No third-party cookies** - Requests are same-origin, eliminating cross-site tracking +- **Works with ad blockers** - Requests appear first-party. Google's [server-side tagging documentation](https://developers.google.com/tag-platform/tag-manager/server-side/intro#benefits) notes that this approach provides more reliable data collection and better control over user privacy. +- **Faster loads** - No extra DNS lookups for external domains + +## How it Works + +When you enable first-party mode: + +1. **Build time**: Nuxt downloads scripts and rewrites URLs to local paths (e.g., `https://www.google-analytics.com/g/collect` → `/_scripts/c/ga/g/collect`) +2. **Runtime**: Nitro route rules proxy requests from local paths back to original endpoints + +``` +User Browser → Your Server (/_scripts/c/ga/...) → Google Analytics +``` + +Your users never connect directly to third-party servers. + +## Usage + +### Enable Globally + +Enable first-party mode for all supported scripts: + +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + scripts: { + firstParty: true, + registry: { + googleAnalytics: { id: 'G-XXXXXX' }, + metaPixel: { id: '123456' }, + } + } +}) +``` + +### Privacy Controls + +Each script in the registry declares its own privacy defaults based on what data it needs. Privacy is controlled by six flags: + +| Flag | What it does | +|------|-------------| +| `ip` | Anonymizes IP addresses to subnet level in headers and payload params | +| `userAgent` | Normalizes User-Agent to browser family + major version (e.g. `Mozilla/5.0 (compatible; Chrome/131.0)`{lang="ts"}) | +| `language` | Normalizes Accept-Language to primary language tag | +| `screen` | Generalizes screen resolution, viewport, hardware concurrency, and device memory to common buckets | +| `timezone` | Generalizes timezone offset and IANA timezone names | +| `hardware` | Anonymizes canvas/webgl/audio fingerprints, plugin/font lists, browser versions, and device info | + +Sensitive headers (`cookie`, `authorization`) are **always** stripped regardless of privacy settings. + +#### Per-Script Defaults + +Scripts declare the privacy that makes sense for their use case: + +| Script | ip | userAgent | language | screen | timezone | hardware | Rationale | +|--------|:--:|:---------:|:--------:|:------:|:--------:|:--------:|-----------| +| Google Analytics | ✓ | - | ✓ | - | - | ✓ | UA/screen/timezone needed for device, time, and OS reports | +| Google Tag Manager | - | - | - | - | - | - | Container script loading - no user data in requests | +| Meta Pixel | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | Untrusted ad network - full anonymization | +| TikTok Pixel | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | Untrusted ad network - full anonymization | +| X/Twitter Pixel | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | Untrusted ad network - full anonymization | +| Snapchat Pixel | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | Untrusted ad network - full anonymization | +| Reddit Pixel | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | Untrusted ad network - full anonymization | +| Segment | - | - | - | - | - | - | Trusted data pipeline - full fidelity required | +| PostHog | - | - | - | - | - | - | Trusted, open-source - full fidelity required | +| Microsoft Clarity | ✓ | - | ✓ | - | - | ✓ | UA/screen/timezone needed for heatmaps and device filtering | +✓ = anonymized, - = passed through + +| Hotjar | ✓ | - | ✓ | - | - | ✓ | UA/screen/timezone needed for heatmaps and device filtering | + +#### Global Override + +Override all per-script defaults at once: + +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + scripts: { + firstParty: { + privacy: true, // Full anonymize for ALL scripts + } + } +}) +``` + +Or selectively override specific flags: + +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + scripts: { + firstParty: { + privacy: { ip: true }, // Anonymize IP for all scripts, rest uses per-script defaults + } + } +}) +``` + +::callout{type="info"} +When a flag is active, data is either **generalized** (reduced precision) or **redacted** (emptied/zeroed) - analytics endpoints still receive valid data. For example, screen resolution `1440x900` becomes `1920x1080` (desktop bucket) and User-Agent is normalized to `Mozilla/5.0 (compatible; Chrome/131.0)`{lang="ts"}, while hardware fingerprints like canvas, WebGL, plugins, and fonts are zeroed or cleared. +:: + +### Custom Paths + +Customize the proxy endpoint paths: + +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + scripts: { + firstParty: { + collectPrefix: '/_analytics', // Default: /_scripts/c + } + } +}) +``` + +### Opt-out Per Script + +Disable first-party routing for a specific script: + +```ts +useScriptGoogleAnalytics({ + id: 'G-XXXXXX', + scriptOptions: { + firstParty: false, // Load directly from Google + } +}) +``` + +## Supported Scripts + +First-party mode supports the following scripts: + +| Script | Endpoints Proxied | +|--------|-------------------| +| [Google Analytics](/scripts/analytics/google-analytics) | `google-analytics.com`, `analytics.google.com`, `stats.g.doubleclick.net`, `pagead2.googlesyndication.com` | +| [Google Tag Manager](/scripts/tracking/google-tag-manager) | `www.googletagmanager.com` | +| [Meta Pixel](/scripts/tracking/meta-pixel) | `connect.facebook.net`, `www.facebook.com/tr`, `pixel.facebook.com` | +| [TikTok Pixel](/scripts/tracking/tiktok-pixel) | `analytics.tiktok.com` | +| [Segment](/scripts/tracking/segment) | `api.segment.io`, `cdn.segment.com` | +| [PostHog](/scripts/analytics/posthog) | `us.i.posthog.com`, `eu.i.posthog.com`, `us-assets.i.posthog.com`, `eu-assets.i.posthog.com` | +| [Microsoft Clarity](/scripts/marketing/clarity) | `www.clarity.ms`, `scripts.clarity.ms`, `d.clarity.ms`, `e.clarity.ms` | +| [Hotjar](/scripts/marketing/hotjar) | `static.hotjar.com`, `script.hotjar.com`, `vars.hotjar.com`, `in.hotjar.com` | +| [X/Twitter Pixel](/scripts/tracking/x-pixel) | `analytics.twitter.com`, `t.co` | +| [Snapchat Pixel](/scripts/tracking/snapchat-pixel) | `tr.snapchat.com` | +| [Reddit Pixel](/scripts/tracking/reddit-pixel) | `alb.reddit.com`, `pixel-config.reddit.com` | +| [Plausible Analytics](/scripts/analytics/plausible-analytics) | `plausible.io` | +| [Cloudflare Web Analytics](/scripts/analytics/cloudflare-web-analytics) | `static.cloudflareinsights.com`, `cloudflareinsights.com` | +| [Rybbit Analytics](/scripts/analytics/rybbit-analytics) | `app.rybbit.io` | +| [Umami Analytics](/scripts/analytics/umami-analytics) | `cloud.umami.is` | +| [Databuddy Analytics](/scripts/analytics/databuddy-analytics) | `cdn.databuddy.cc`, `basket.databuddy.cc` | +| [Fathom Analytics](/scripts/analytics/fathom-analytics) | `cdn.usefathom.com` | +| [Vercel Analytics](/scripts/analytics/vercel-analytics) | `va.vercel-scripts.com` | +| [Intercom](/scripts/support/intercom) | `widget.intercom.io`, `api-iam.intercom.io` | +| [Crisp](/scripts/support/crisp) | `client.crisp.chat` | + +## Requirements + +First-party mode requires a **server runtime**. It won't work with fully static hosting (e.g., `nuxt generate` to GitHub Pages) because the proxy endpoints need a server to forward requests. + +For static deployments, you can still enable first-party mode - Nuxt bundles scripts with rewritten URLs, but you'll need to configure your hosting platform's rewrite rules manually. + +### Static Hosting Rewrites + +If deploying statically, configure your platform to proxy these paths: + +``` +/_scripts/c/ga/* → https://www.google.com/* +/_scripts/c/gtm/* → https://www.googletagmanager.com/* +/_scripts/c/meta/* → https://connect.facebook.net/* +``` + +## First-Party vs Bundle + +First-party mode supersedes the `bundle` option: + +| Feature | `bundle: true` | `firstParty: true` | +|---------|---------------|-------------------| +| Downloads script at build | ✅ | ✅ | +| Serves from your domain | ✅ | ✅ | +| Rewrites collection URLs | ❌ | ✅ | +| Proxies API requests | ❌ | ✅ | +| Hides user IPs | ❌ | ✅ | +| Blocks third-party cookies | ❌ | ✅ | + +The `bundle` option only self-hosts the script file. First-party mode also rewrites and proxies all collection/tracking endpoints, providing complete first-party routing. + +::callout{type="warning"} +The `bundle` option is deprecated. Use `firstParty: true` for new projects. +:: + +## Default Behavior + +First-party mode is **enabled by default**. For static hosting (GitHub Pages, etc.), Nuxt still bundles scripts but you'll need to configure platform rewrites for the proxy endpoints to work. + +To disable first-party mode: + +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + scripts: { + firstParty: false + } +}) +``` + +## Platform Rewrites + +When deploying to static hosting or edge platforms, configure these rewrites to proxy collection endpoints. + +### Vercel + +```json [vercel.json] +{ + "rewrites": [ + { "source": "/_scripts/c/ga/:path*", "destination": "https://www.google.com/:path*" }, + { "source": "/_scripts/c/ga-legacy/:path*", "destination": "https://www.google-analytics.com/:path*" }, + { "source": "/_scripts/c/gtm/:path*", "destination": "https://www.googletagmanager.com/:path*" }, + { "source": "/_scripts/c/meta/:path*", "destination": "https://connect.facebook.net/:path*" }, + { "source": "/_scripts/c/tiktok/:path*", "destination": "https://analytics.tiktok.com/:path*" }, + { "source": "/_scripts/c/segment/:path*", "destination": "https://api.segment.io/:path*" }, + { "source": "/_scripts/c/clarity/:path*", "destination": "https://www.clarity.ms/:path*" }, + { "source": "/_scripts/c/hotjar/:path*", "destination": "https://static.hotjar.com/:path*" }, + { "source": "/_scripts/c/x/:path*", "destination": "https://analytics.twitter.com/:path*" }, + { "source": "/_scripts/c/snap/:path*", "destination": "https://tr.snapchat.com/:path*" }, + { "source": "/_scripts/c/reddit/:path*", "destination": "https://alb.reddit.com/:path*" } + ] +} +``` + +### Netlify + +```toml [netlify.toml] +[[redirects]] +from = "/_scripts/c/ga/*" +to = "https://www.google.com/:splat" +status = 200 + +[[redirects]] +from = "/_scripts/c/ga-legacy/*" +to = "https://www.google-analytics.com/:splat" +status = 200 + +[[redirects]] +from = "/_scripts/c/gtm/*" +to = "https://www.googletagmanager.com/:splat" +status = 200 + +[[redirects]] +from = "/_scripts/c/meta/*" +to = "https://connect.facebook.net/:splat" +status = 200 + +# Add more as needed... +``` + +### Cloudflare Pages + +Create a `_redirects` file in your public directory: + +```txt [public/_redirects] +/_scripts/c/ga/* https://www.google.com/:splat 200 +/_scripts/c/ga-legacy/* https://www.google-analytics.com/:splat 200 +/_scripts/c/gtm/* https://www.googletagmanager.com/:splat 200 +/_scripts/c/meta/* https://connect.facebook.net/:splat 200 +``` + +Or use Cloudflare Workers for more control: + +```ts [functions/_scripts/c/[[path]].ts] +export const onRequest: PagesFunction = async (context) => { + const url = new URL(context.request.url) + const path = url.pathname.replace('/_scripts/c/', '') + + // Route based on prefix + const routes: Record = { + 'ga/': 'https://www.google.com/', + 'gtm/': 'https://www.googletagmanager.com/', + 'meta/': 'https://connect.facebook.net/', + } + + for (const [prefix, target] of Object.entries(routes)) { + if (path.startsWith(prefix)) { + const targetUrl = target + path.slice(prefix.length) + url.search + return fetch(targetUrl, context.request) + } + } + + return new Response('Not found', { status: 404 }) +} +``` + +## Architecture Diagram + +``` +┌─────────────────────────────────────────────────────────────────────┐ +│ BUILD TIME │ +├─────────────────────────────────────────────────────────────────────┤ +│ │ +│ 1. Download script from third-party │ +│ https://www.googletagmanager.com/gtag/js?id=G-XXX │ +│ ↓ │ +│ 2. Rewrite URLs in script content │ +│ "www.google.com/g/collect" → "/_scripts/c/ga/g/collect" │ +│ ↓ │ +│ 3. Save rewritten script to build output │ +│ .output/public/_scripts/abc123.js │ +│ │ +└─────────────────────────────────────────────────────────────────────┘ + ↓ +┌─────────────────────────────────────────────────────────────────────┐ +│ RUNTIME │ +├─────────────────────────────────────────────────────────────────────┤ +│ │ +│ User Browser │ +│ │ │ +│ │ 1. Request script │ +│ │ GET /_scripts/abc123.js │ +│ ↓ │ +│ Your Server (Nitro) │ +│ │ │ +│ │ 2. Serve bundled script (rewritten URLs) │ +│ ↓ │ +│ User Browser │ +│ │ │ +│ │ 3. Script sends analytics │ +│ │ POST /_scripts/c/ga/g/collect │ +│ ↓ │ +│ Your Server (Nitro route rule) │ +│ │ │ +│ │ 4. Proxy to third-party │ +│ │ POST https://www.google.com/g/collect │ +│ ↓ │ +│ Third-Party Server │ +│ │ │ +│ │ 5. Sees YOUR server's IP, not user's IP │ +│ ↓ │ +│ Response proxied back to user │ +│ │ +└─────────────────────────────────────────────────────────────────────┘ +``` + +## Troubleshooting + +### Analytics Not Tracking + +**Symptoms**: Events not appearing in analytics dashboard. + +**Check these:** + +1. **Verify proxy routes are active** - In dev mode, check `/_scripts/status.json` or the DevTools Scripts panel +2. **Check browser Network tab** - Look for requests to `/_scripts/c/` paths and verify they return 200 +3. **Check server logs** - Look for proxy errors in Nitro logs +4. **Verify route rules** - Run `npx nuxi build` and check `.output/server/chunks/routes/rules.mjs` + +```ts +// Debug: Log all requests in server middleware +export default defineEventHandler((event) => { + if (event.path.startsWith('/_scripts/c/')) { + console.log('Proxying:', event.path) + } +}) +``` + +### CORS Errors + +**Symptoms**: Browser console shows CORS errors for analytics requests. + +**Solutions:** + +1. Ensure you correctly configure route rules in `nuxt.config.ts` +2. For static hosting, verify platform rewrites are set up +3. Check that the target URLs in rewrites match exactly + +### Cached Old Script + +**Symptoms**: Script content is stale or URLs aren't rewritten. + +**Solutions:** + +1. Clear the build cache: + ```bash + rm -rf .nuxt/cache/scripts + npx nuxi build + ``` + +2. Force re-download during development: + ```ts + useScriptGoogleAnalytics({ + id: 'G-XXX', + scriptOptions: { bundle: 'force' } + }) + ``` + +### Static Build Not Proxying + +**Symptoms**: Scripts load but analytics don't track on static hosting. + +This behavior is normal - Static builds cannot proxy requests. Configure platform rewrites (see Platform Rewrites section above) or switch to server-rendered mode. + +### Script Download Fails + +**Symptoms**: Build fails with network timeout errors. + +**Solutions:** + +1. Enable fallback mode: + ```ts [nuxt.config.ts] + export default defineNuxtConfig({ + scripts: { + firstParty: true, + assets: { + fallbackOnSrcOnBundleFail: true + } + } + }) + ``` + +2. Increase timeout: + ```ts [nuxt.config.ts] + export default defineNuxtConfig({ + scripts: { + assets: { + fetchOptions: { + timeout: 30000 // 30 seconds + } + } + } + }) + ``` + +## FAQ + +### Does first-party mode bypass GDPR consent requirements? + +**No.** First-party mode changes where requests are routed, not whether tracking occurs. You still need user consent before loading tracking scripts. Use consent triggers: + +```ts +const { accept } = useScriptTriggerConsent() + +// Only load after user consents +function onConsentGiven() { + accept() +} +``` + +### Will analytics still work if the third-party changes their script? + +**Yes, with automatic updates.** Nuxt caches scripts for 7 days by default. When the cache expires, the script is re-downloaded and URLs are rewritten again. You can customize cache duration: + +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + scripts: { + assets: { + cacheMaxAge: 86400000 // 1 day in ms + } + } +}) +``` + +### Can I customize proxy paths? + +**Yes.** Use the `collectPrefix` option: + +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + scripts: { + firstParty: { + collectPrefix: '/_tracking' // Instead of /_scripts/c + } + } +}) +``` + +### Does this work with Server-Side Tracking? + +First-party mode is for client-side scripts. For server-side tracking (Measurement Protocol, etc.), send requests directly from your server endpoints without the proxy layer. + +### How do I debug what's being proxied? + +1. **DevTools**: Open Nuxt DevTools → Scripts → First-Party tab +2. **Status endpoint**: Visit `/_scripts/status.json` in dev mode +3. **Console logs**: Enable debug mode: + ```ts [nuxt.config.ts] + export default defineNuxtConfig({ + scripts: { + debug: true + } + }) + ``` + +### Is there a performance impact? + +**Minimal.** The proxy adds a small latency (~10-50ms) as requests go through your server. However, this is often offset by: +- Eliminating DNS lookups for third-party domains +- Bypassing ad blockers that would otherwise block requests +- Reduced connection overhead (reusing existing connections) + +### Which scripts can I add first-party support to? + +Currently, first-party mode supports all scripts listed in the Supported Scripts section. For other scripts, you can: + +1. Request support by opening an issue +2. Use the `bundle` option for self-hosting without proxy (deprecated) +3. Configure custom route rules manually + +## Hybrid Rendering + +First-party mode works with Nuxt's hybrid rendering features: + +### Route-Level SSR + +When using `routeRules` to disable SSR for specific routes, first-party mode still works because: + +1. Nuxt bundles scripts at build time (not runtime) +2. Proxy route rules are global Nitro configuration +3. Collection requests still go through your server + +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + routeRules: { + '/dashboard/**': { ssr: false }, // SPA mode for dashboard + }, + scripts: { + firstParty: true, // Still works for all routes + } +}) +``` + +### ISR (Incremental Static Regeneration) + +ISR pages work with first-party mode. The bundled scripts are served from static assets, while collection requests are proxied through Nitro at runtime. + +### Edge Rendering + +First-party mode works with edge deployments (Cloudflare Workers, Vercel Edge, etc.). The proxy routes use Nitro's native proxy support which works across all deployment targets. + +::callout{type="info"} +For edge deployments, ensure your edge runtime supports outbound fetch requests to the analytics endpoints. +:: + +## Consent Integration + +First-party mode and consent management are complementary features: + +- **First-party mode** controls *where* requests go (through your server vs direct) +- **Consent triggers** control *when* scripts load (before vs after consent) + +### Using Both Together + +```vue + +``` + +### Consent Banner Example + +```vue + + + +``` + +### Privacy Flow + +``` +User visits site + │ + ↓ +Scripts registered but NOT loaded (consent pending) + │ + ↓ +User accepts cookies + │ + ↓ +Scripts load from YOUR server (/_scripts/...) + │ + ↓ +Analytics sent through YOUR server (/_scripts/c/...) + │ + ↓ +Third-party sees YOUR server's IP, not user's +``` + +This gives you both GDPR compliance (consent before tracking) and enhanced privacy (first-party routing after consent). + +## Health Check + +To verify first-party mode is working correctly: + +### 1. Check DevTools + +Open Nuxt DevTools → Scripts → First-Party tab to see: +- Enabled status +- Configured scripts +- Active proxy routes + +### 2. Check Status Endpoint + +In development, visit `/_scripts/status.json`: + +```json +{ + "enabled": true, + "scripts": ["googleAnalytics", "metaPixel"], + "routes": { + "/_scripts/c/ga/**": "https://www.google.com/**", + "/_scripts/c/meta/**": "https://connect.facebook.net/**" + }, + "collectPrefix": "/_scripts/c" +} +``` + +### 3. Verify in Browser + +1. Open browser DevTools → Network tab +2. Filter by `/_scripts` +3. Trigger an analytics event +4. Verify requests go to `/_scripts/c/...` paths (not third-party domains) +5. Check response status is 200 + +### 4. CLI Status + +Run the CLI command to check cache status: + +```bash +npx nuxt-scripts status +``` + +This shows cached scripts and their sizes. diff --git a/docs/content/docs/1.guides/3.bundling.md b/docs/content/docs/1.guides/3.bundling.md new file mode 100644 index 00000000..7b985bcf --- /dev/null +++ b/docs/content/docs/1.guides/3.bundling.md @@ -0,0 +1,336 @@ +--- + +title: Bundling Remote Scripts +description: Optimize third-party scripts by bundling them with your app. + +--- + +::callout{type="warning"} +The `bundle` option is deprecated in favor of [First-Party Mode](/docs/guides/first-party), which provides the same benefits plus routed collection endpoints for improved privacy. Use `firstParty: true` for new projects. +:: + +## Background + +When you use scripts from other sites on your website, you rely on another server to load these scripts. This can slow down your site and raise concerns about safety and privacy. + +### Common problems + +- Slower website because it takes time to connect to other servers. +- Safety risks if the other server is hacked. +- Other servers may use your visitors' data inappropriately. +- Ad blockers or privacy tools might stop these scripts from working. + +### How to fix it + +By bundling these scripts, you can host them yourself, which helps avoid these issues and keeps your site running smoothly. + +## How it Works + +During the build process, Nuxt checks your code to find any instances of [`useScript()`{lang="ts"}](/docs/api/use-script){lang="ts"} that need bundling. + +When Nuxt identifies a script for bundling, it downloads and saves it as a public asset at `/_scripts/[hash].js`. Here, `[hash]` represents the hash of the script's URL. + +**Important points about bundling:** + +1. You need to have static values for your script URLs and bundling settings. + +::code-group + +```ts [Input - Pre Build] +// GOOD - Static values allow for bundling +useScript('https://example.com/script.js', { + bundle: true +}) +// BAD - Dynamic values prevent bundling +useScript(scriptSrc, { + bundle: canBundle +}) +``` + +```ts [Output - Post Build] +// GOOD - Nuxt bundles the script +useScript('/_scripts/[hash].js', {}) +// BAD - Nuxt does not bundle the script (remains the same) +useScript(scriptSrc, { + bundle: canBundle +}) +``` + +:: + +2. If the original script changes without a URL change, the bundled version won't update in the browser cache. To handle this, use a versioned URL or a cache-busting query parameter. + +## Usage + +You can bundle scripts individually or on a global scale using specific settings. + +### Script Options + +To decide if Nuxt can bundle an individual script, use the `bundle` option. + +::code-group + +```ts [useScript] +// Opt-in to bundle this specific script +useScript('https://example.com/script.js', { + bundle: true, +}) + +// Force download bypassing cache +useScript('https://example.com/script.js', { + bundle: 'force', +}) +``` + +```ts [Registry Script] +// Registry script bundling using scriptOptions +useScriptGoogleAnalytics({ + id: 'GA_MEASUREMENT_ID', + scriptOptions: { + bundle: true + } +}) + +// bundle without cache +useScriptGoogleAnalytics({ + id: 'GA_MEASUREMENT_ID', + scriptOptions: { + bundle: 'force' + } +}) +``` +:: + +#### Bundle Options + +The `bundle` option accepts the following values: + +- `false` - Do not bundle the script (default) +- `true` - Bundle the script and use cached version if available +- `'force'` - Bundle the script and force download, bypassing cache + +**Note**: Using `'force'` will re-download scripts on every build, which may increase build time and provide less security. + +### Global Bundling + +Adjust the default behavior for all scripts using the Nuxt Config. This example sets Nuxt to bundle all scripts by default. + +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + scripts: { + defaultScriptOptions: { + bundle: true, + } + } +}) +``` + +### Build-time vs Runtime Behavior + +Understanding when bundling happens and how it affects runtime behavior is crucial for effective usage. + +#### Build-time Processing + +Bundling occurs during the build phase through static code analysis: + +```ts +// ✅ Nuxt bundles at build-time (static values) +useScript('https://example.com/script.js', { bundle: true }) + +// ❌ Nuxt cannot bundle (dynamic values) +const scriptUrl = computed(() => getScriptUrl()) +useScript(scriptUrl, { bundle: dynamic.value }) +``` + +#### Runtime Behavior + +At runtime, bundled scripts behave differently: + +```ts +// Original code +useScript('https://example.com/script.js', { bundle: true }) + +// After build transformation +useScript('/_scripts/abc123.js', {}) +``` + +**Important**: Once Nuxt bundles the script, you lose access to the original URL at runtime. If you need the original URL for tracking or analytics, store it separately. + +#### Static URL Requirements + +For bundling to work, the transformer requires **static values**: + +::code-group + +```ts [✅ Valid for Bundling] +// Static string literals +useScript('https://cdn.example.com/lib.js', { bundle: true }) + +// Static template literals (no variables) +useScript(`https://cdn.example.com/lib.js`, { bundle: true }) + +// Constants defined at module level +const SCRIPT_URL = 'https://cdn.example.com/lib.js' +useScript(SCRIPT_URL, { bundle: true }) +``` + +```ts [❌ Nuxt Cannot Bundle] +// Runtime variables +const url = getScriptUrl() +useScript(url, { bundle: true }) + +// Computed values +const scriptUrl = computed(() => `https://cdn.example.com/${version.value}.js`) +useScript(scriptUrl, { bundle: true }) + +// Environment variables at runtime +useScript(process.env.SCRIPT_URL, { bundle: true }) + +// Props or reactive values +useScript(props.scriptUrl, { bundle: true }) +``` + +:: + +#### Manual Injection Patterns + +When automatic bundling isn't possible, you can manually inject bundled scripts: + +```ts [Manual Bundling Workaround] +// 1. Bundle during build with static URL +const staticScript = useScript('https://cdn.example.com/static.js', { + bundle: true, + trigger: 'manual' // Don't auto-load +}) + +// 2. Conditionally load based on runtime logic +function loadScript() { + if (shouldLoadScript.value) { + staticScript.load() + } +} + +// 3. Alternative: Use multiple static configurations +const scriptVariants = { + dev: useScript('https://cdn.example.com/dev.js', { bundle: true, trigger: 'manual' }), + prod: useScript('https://cdn.example.com/prod.js', { bundle: true, trigger: 'manual' }) +} + +// Load appropriate variant +const currentScript = computed(() => + isDev ? scriptVariants.dev : scriptVariants.prod +) +``` + +#### Working with Dynamic URLs + +For truly dynamic scenarios, consider these patterns: + +```ts [Dynamic URL Strategies] +// Option 1: Pre-bundle known variants +const analytics = { + google: useScript('https://www.googletagmanager.com/gtag/js', { bundle: true }), + plausible: useScript('https://plausible.io/js/script.js', { bundle: true }) +} + +// Option 2: Fallback to runtime loading +function loadDynamicScript(url: string) { + // Nuxt won't bundle this, but it will work at runtime + return useScript(url, { + bundle: false, // Explicitly disable + trigger: 'manual' + }) +} + +// Option 3: Use server-side bundling +// Store script content in your bundle and inject manually +const { $script } = useNuxtApp() +$script.add({ + innerHTML: await $fetch('/api/dynamic-script-content'), +}) +``` + +### Asset Configuration + +Use the `assets` option in your configuration to customize how Nuxt bundles and caches scripts. + +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + scripts: { + assets: { + prefix: '/_custom-script-path/', + cacheMaxAge: 86400000, // 1 day in milliseconds + integrity: true, // Enable SRI hash generation + } + } +}) +``` + +#### Available Options + +- **`prefix`** - Custom path where Nuxt serves bundled scripts (default: `/_scripts/`) +- **`cacheMaxAge`** - Cache duration for bundled scripts in milliseconds (default: 7 days) +- **`integrity`** - Enable automatic SRI (Subresource Integrity) hash generation (default: `false`) + +#### Cache Behavior + +The bundling system uses two different cache strategies: + +- **Build-time cache**: Controlled by `cacheMaxAge` (default: 7 days). Nuxt re-downloads scripts older than this during builds to ensure freshness. +- **Runtime cache**: Nuxt serves bundled scripts with 1-year cache headers since they are content-addressed by hash. + +This dual approach ensures both build performance and reliable browser caching. + +### Subresource Integrity (SRI) + +Subresource Integrity (SRI) is a security feature that ensures scripts haven't been tampered with. According to the [W3C SRI specification](https://www.w3.org/TR/SRI/), it allows browsers to verify that resources fetched are delivered without unexpected manipulation. When enabled, Nuxt calculates a cryptographic hash for each bundled script and adds it as an `integrity` attribute. + +#### Enabling SRI + +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + scripts: { + assets: { + integrity: true, // Uses sha384 by default + } + } +}) +``` + +#### Hash Algorithms + +You can specify the hash algorithm: + +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + scripts: { + assets: { + integrity: 'sha384', // Default, recommended balance of security/size + // integrity: 'sha256', // Smaller hash + // integrity: 'sha512', // Strongest security + } + } +}) +``` + +#### How It Works + +When you enable integrity: + +1. During build, Nuxt hashes each bundled script's content +2. Nuxt stores the hash in the build cache for reuse +3. Nuxt injects the `integrity` attribute into the script tag +4. Nuxt automatically adds the `crossorigin="anonymous"` attribute (required by browsers for SRI) + +```html + + +``` + +#### Security Benefits + +- **Tamper detection**: Browser refuses to execute scripts if the hash doesn't match +- **CDN compromise protection**: Even if your CDN is compromised, modified scripts won't execute +- **Build-time verification**: Nuxt calculates the hash from the actual downloaded content diff --git a/docs/content/docs/1.guides/3.consent.md b/docs/content/docs/1.guides/3.consent.md index c247750e..b7b2f808 100644 --- a/docs/content/docs/1.guides/3.consent.md +++ b/docs/content/docs/1.guides/3.consent.md @@ -1,21 +1,27 @@ --- + title: Consent Management description: Learn how to get user consent before loading scripts. + --- +::callout{icon="i-heroicons-play" to="https://stackblitz.com/github/nuxt/scripts/tree/main/examples/cookie-consent" target="_blank"} +Try the live [Cookie Consent Example](https://stackblitz.com/github/nuxt/scripts/tree/main/examples/cookie-consent) or [Granular Consent Example](https://stackblitz.com/github/nuxt/scripts/tree/main/examples/granular-consent) on [StackBlitz](https://stackblitz.com). +:: + ## Background -Many third-party scripts include tracking cookies that require user consent under privacy laws. Nuxt Scripts simplifies this process with the [useScriptTriggerConsent](/docs/api/use-script-trigger-consent) composable, allowing scripts to be loaded only after receiving user consent. +Many third-party scripts include tracking cookies that require user consent under privacy laws. Nuxt Scripts simplifies this process with the [`useScriptTriggerConsent()`{lang="ts"}](/docs/api/use-script-trigger-consent){lang="ts"} composable, allowing scripts to load only after you receive user consent. ## Usage -The [useScriptTriggerConsent](/docs/api/use-script-trigger-consent) composable offers flexible interaction options suitable for various scenarios. +The [`useScriptTriggerConsent()`{lang="ts"}](/docs/api/use-script-trigger-consent){lang="ts"} composable offers flexible interaction options suitable for various scenarios. See the [API](/docs/api/use-script-trigger-consent) docs for full details on the available options. ### Accepting as a Function -The easiest way to make use of `useScriptTriggerConsent` is by invoking the `accept` method when user consent is granted. +The easiest way to make use of [`useScriptTriggerConsent()`{lang="ts"}](/docs/api/use-script-trigger-consent){lang="ts"} is by invoking the `accept` method when user consent is granted. For an example of how you might lay out your code to handle this, see the following: @@ -51,7 +57,7 @@ import { agreedToCookiesScriptConsent } from '#imports' ### Accepting as a resolvable boolean -Alternatively, you can pass a reactive reference to the consent state to the `useScriptTriggerConsent` composable. This will automatically load the script when the consent state is `true`. +Alternatively, you can pass a reactive reference to the consent state to the [`useScriptTriggerConsent()`{lang="ts"}](/docs/api/use-script-trigger-consent){lang="ts"} composable. This will automatically load the script when the consent state is `true`. ```ts const agreedToCookies = ref(false) @@ -66,7 +72,7 @@ useScript('https://www.google-analytics.com/analytics.js', { There may be instances where you want to trigger the script load after a certain event, only if the user has consented. -For this you can use the `postConsentTrigger`, which shares the same API as `trigger` from the `useScript` composable. +For this you can use the `postConsentTrigger`, which shares the same API as `trigger` from the [`useScript()`{lang="ts"}](/docs/api/use-script){lang="ts"} composable. ```ts const agreedToCookies = ref(false) @@ -74,7 +80,9 @@ useScript('https://www.google-analytics.com/analytics.js', { trigger: useScriptTriggerConsent({ consent: agreedToCookies, // load 3 seconds after consent is granted - postConsentTrigger: new Promise(resolve => setTimeout(resolve, 3000)) + postConsentTrigger: () => new Promise(resolve => + setTimeout(resolve, 3000), + ), }) }) ``` diff --git a/docs/content/docs/1.guides/3.page-events.md b/docs/content/docs/1.guides/3.page-events.md index c7dad1a9..830237af 100644 --- a/docs/content/docs/1.guides/3.page-events.md +++ b/docs/content/docs/1.guides/3.page-events.md @@ -1,6 +1,8 @@ --- + title: Script Event Page description: Learn how to send page events to your analytics provider. + --- ## Background @@ -8,13 +10,13 @@ description: Learn how to send page events to your analytics provider. When using tracking scripts, it's common to send an event when the page changes. Due to Nuxt's head implementation being async, the page title is not always available on route change immediately. -Nuxt Scripts provides the [useScriptEventPage](/docs/api/use-script-event-page) composable to solve this problem. +Nuxt Scripts provides the [`useScriptEventPage()`{lang="ts"}](/docs/api/use-script-event-page){lang="ts"} composable to solve this problem. See the [API](/docs/api/use-script-event-page) docs for full details on the available options. ### Usage -The composable works by providing a function that will be invoked whenever the page changes, providing the newly resolved +The composable works by providing a function that Nuxt invokes whenever the page changes, providing the newly resolved title and path. You can use this with any analytics provider where you're seeing the page title not being accurate on route change. diff --git a/docs/content/docs/1.guides/4.global.md b/docs/content/docs/1.guides/4.global.md index 562faab6..e5926e8a 100644 --- a/docs/content/docs/1.guides/4.global.md +++ b/docs/content/docs/1.guides/4.global.md @@ -1,6 +1,8 @@ --- + title: Global Scripts description: Load global third-party scripts and optimize them for your Nuxt app. + --- ## Background @@ -10,7 +12,7 @@ While the `app.head` method in Nuxt Config allows for loading global scripts, it ```ts export default defineNuxtConfig({ head: { - script: [ { src: 'https://analytics.com/tracker.js', async: true } ] + script: [{ src: 'https://analytics.com/tracker.js', async: true }] } }) ``` @@ -22,13 +24,13 @@ You may consider using global scripts when: - You don't care about interacting with the API provided by the third-party script (e.g. you don't need to use `gtag` from Google Analytics). - You are interacting with the API provided by the third-party script, but you don't care about type safety. -Otherwise, it's recommended to use [useScript](/docs/api/use-script) to load scripts in a safer way. +Otherwise, it's recommended to use [`useScript()`{lang="ts"}](/docs/api/use-script){lang="ts"} to load scripts in a safer way. ## Usage The `globals` key supports strings, objects and arrays. -**Example: Load a script using just the src** +**Example: Load a script using the src** ```ts export default defineNuxtConfig({ @@ -74,7 +76,7 @@ export default defineNuxtConfig({ ### Accessing a global script -All Nuxt Scripts are registered on the `$scripts` Nuxt App property. +All Nuxt Scripts register on the `$scripts` Nuxt App property. For scripts registered through nuxt.config, type autocompletion is available. @@ -87,9 +89,9 @@ $scripts.myScript // { $script, instance } ## How it Works -The `globals` configuration will be used to create a virtual Nuxt plugin that loads in the script using the `useScript` composable. +Nuxt uses the `globals` configuration to create a virtual Nuxt plugin that loads in the script using the [`useScript()`{lang="ts"}](/docs/api/use-script){lang="ts"} composable. -As `useScript` is being used under the hood, it's important to understand the defaults and behavior of the [useScript](/api/use-script) composable.. +As Nuxt uses [`useScript()`{lang="ts"}](/docs/api/use-script){lang="ts"} under the hood, it's important to understand the defaults and behavior of the [`useScript()`{lang="ts"}](/docs/api/use-script){lang="ts"} composable.. ::code-group diff --git a/docs/content/docs/1.guides/5.facade-components.md b/docs/content/docs/1.guides/5.facade-components.md index 7a3d17c2..8f120c43 100644 --- a/docs/content/docs/1.guides/5.facade-components.md +++ b/docs/content/docs/1.guides/5.facade-components.md @@ -1,9 +1,11 @@ --- + title: Facade Components description: Facade Components are fake UI elements that get replaced once a third-party script loads. + --- -Nuxt Scripts provides several Facade Components that you can use to speed up your app's performance. +Nuxt Scripts provides several Facade Components that you can use to speed up your app's performance. Using them has trade-offs, but they can aid in improving the performance experience of your app. @@ -12,7 +14,7 @@ Using them has trade-offs, but they can aid in improving the performance experie To render complex components using third-party scripts such as a Video embed, payment modal, or chat widget, we need many resources. Loading these while Nuxt is starting will slow down your app's performance. -However, if we delay loading the script until Nuxt is finished, we end up with harmful content layout shifts (CLS) and visual noise, +However, if we delay loading the script until Nuxt finishes, we end up with harmful content layout shifts (CLS) and visual noise, leading to a poor user experience. Facade Components aim to solve this by rendering a "fake" UI element that gets replaced once the third-party script loads. @@ -21,17 +23,17 @@ By hooking into appropriate DOM events and providing user feedback, we can use t ## What are the trade-offs in using Facade Components? -While the performance benefit is quite clear, there can be trade-off on user experience. +While the performance benefit is clear, there can be trade-off on user experience. -- **Flash of mismatched content**: The fake UI element may not look like the final UI element, leading to a flash of mis-matched content. Only minimal -styling is provided by Nuxt Scripts, you may need to tweak it to match your app's design. +- **Flash of mismatched content**: The fake UI element may not look like the final UI element, leading to a flash of mis-matched content. Nuxt Scripts provides only minimal +styling, you may need to tweak it to match your app's design. - **Interactivity can break**: Interactivity of the elements requires the script to load, if it doesn't load then you need to provide a fallback. - **Accessibility Concerns**: We need to provide clear a11y feedback to the user when the script is loading or fails to load. ## Nuxt Scripts Facade Components -All Facade Components are headless components that wrap the relevant `useScript` composable. Minimal styling is -provided within the docs to give you a starting point. +All Facade Components are headless components that wrap the relevant `useScript`{lang="html"} composable. Nuxt provides minimal styling +within the docs to give you a starting point. ## Best Practices in using Facade Components @@ -51,7 +53,7 @@ If the script fails to load, provide a fallback that informs the user of the fai When the script is loading, provide a loading state that informs the user that the content is loading. -The `ScriptLoadingIndicator` component is provided by Nuxt Scripts to help with this and provides a11y feedback. +Nuxt Scripts provides the ScriptLoadingIndicator component to help with this and provides a11y feedback. ```vue @@ -110,9 +112,9 @@ The component provides minimal UI by default, only enough to be functional and a @@ -132,5 +134,5 @@ The component provides minimal UI by default, only enough to be functional and a ### Events -- `ready` - Emitted when the script has loaded. Gives you access to the underlying script API. -- `error` - Emitted when the script fails to load. +- `ready` - Emitted when the script has loaded. Gives you access to the basic script API. +- `error` - Emitted when the script fails to load. diff --git a/docs/content/docs/1.guides/6.cors.md b/docs/content/docs/1.guides/6.cors.md new file mode 100644 index 00000000..00081ab3 --- /dev/null +++ b/docs/content/docs/1.guides/6.cors.md @@ -0,0 +1,170 @@ +--- + +title: CORS and Security Attributes +description: Understanding how Nuxt Scripts handles cross-origin security. + +--- + +## Background + +When loading scripts from external domains, browsers enforce Cross-Origin Resource Sharing (CORS) policies. CORS controls how resources on one domain can be requested by scripts running on another domain. For third-party scripts, this affects: + +- Whether the browser sends cookies with requests +- Access to error details for debugging +- Subresource Integrity (SRI) validation + +## Default Behavior + +Nuxt Scripts applies privacy-focused defaults to all scripts: + +```html + +``` + +These defaults: +- **`crossorigin="anonymous"`** - Prevents the script from sending cookies to third-party servers +- **`referrerpolicy="no-referrer"`** - Prevents sharing the page URL with third-party servers + +This improves user privacy but may break scripts that require cookies or referrer information. + +## Common CORS Errors + +### Script Fails to Load + +```text +Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource +``` + +This occurs when a server doesn't return proper CORS headers but `crossorigin="anonymous"` is set. Some third-party scripts don't support CORS. + +### Script Loads but Functions Fail + +The script loads but functionality is broken because it expected cookies or session data. + +### Error Details Hidden + +```js +window.onerror = msg => console.log(msg) +// Shows: "Script error." instead of actual error +``` + +Without `crossorigin`, browsers hide error details from external scripts for security. + +## Configuring CORS Attributes + +### Per-Script Configuration + +Disable CORS attributes for scripts that don't support them: + +```ts +useScript({ + src: 'https://example.com/script.js', + crossorigin: false, // Remove crossorigin attribute + referrerpolicy: false, // Remove referrerpolicy attribute +}) +``` + +Or use a different `crossorigin` value: + +```ts +useScript({ + src: 'https://example.com/script.js', + crossorigin: 'use-credentials', // Send cookies with request +}) +``` + +### Global Configuration + +Change defaults for all scripts: + +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + scripts: { + defaultScriptOptions: { + crossorigin: false, + referrerpolicy: false, + } + } +}) +``` + +## Crossorigin Values + +| Value | Cookies Sent | Error Details | Use Case | +|-------|-------------|---------------|----------| +| `anonymous` | No | Yes (if server supports) | Privacy-focused default | +| `use-credentials` | Yes | Yes | Scripts requiring auth | +| `false` | Yes | No | Scripts without CORS support | + +## Registry Scripts + +Many registry scripts already disable CORS attributes because the third-party doesn't support them: + +```ts +const config = { + scriptInput: { + src: 'https://js.stripe.com/basil/stripe.js', + crossorigin: false, + referrerpolicy: false, + } +} +``` + +Scripts with `crossorigin: false` include: +- Stripe +- YouTube Player +- Google Sign-In +- Google reCAPTCHA +- Meta Pixel +- TikTok Pixel +- X (Twitter) Pixel +- Snapchat Pixel +- Cloudflare Web Analytics +- Lemon Squeezy +- Matomo Analytics + +If a registry script fails, check if CORS configuration needs adjustment. + +## Subresource Integrity + +When using [bundled scripts with SRI](/docs/guides/bundling#subresource-integrity-sri), you must include `crossorigin="anonymous"`, which Nuxt adds automatically: + +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + scripts: { + assets: { + integrity: true, // Automatically sets crossorigin="anonymous" + } + } +}) +``` + +## Troubleshooting + +### Script Won't Load + +1. Check browser console for CORS errors +2. Set `crossorigin: false` to disable CORS mode +3. Verify the third-party server supports CORS headers + +### Script Loads but Broken + +1. The script may require cookies - try `crossorigin: 'use-credentials'` +2. The script may need the referrer - set `referrerpolicy: false` +3. Check if the script expects you to load it without CORS attributes + +### Debugging External Script Errors + +To see full error messages from external scripts: + +1. Ensure the script has `crossorigin="anonymous"` +2. Verify the server returns `Access-Control-Allow-Origin` header +3. If the server doesn't support CORS, you won't get detailed errors + +### Bundling as Alternative + +If CORS issues persist, consider [bundling the script](/docs/guides/bundling) to serve it from your own domain, eliminating CORS entirely. diff --git a/docs/content/docs/1.guides/6.v1-migration.md b/docs/content/docs/1.guides/6.v1-migration.md new file mode 100644 index 00000000..1229809c --- /dev/null +++ b/docs/content/docs/1.guides/6.v1-migration.md @@ -0,0 +1,202 @@ +--- + +title: v1 Migration Guide +description: Migration guide for upgrading from v0.x to v1.0. + +--- + +## What's New in v1 + +v1 focuses on **privacy** and **performance**: route analytics through your own domain, move scripts off the main thread, and render social embeds server-side. + +### First-Party Mode + +Route all script traffic through your own domain for privacy and ad-blocker compatibility. + +```ts +export default defineNuxtConfig({ + scripts: { + firstParty: true, + registry: { + googleAnalytics: { id: 'G-XXXXXX' }, + metaPixel: { id: '123456' }, + } + } +}) +``` + +- **User IPs stay private**: third parties see your server's IP +- **No third-party cookies**: requests are same-origin +- **Works with ad blockers**: requests appear first-party + +Supported: Google Analytics, GTM, Meta Pixel, TikTok, Segment, Clarity, Hotjar, X/Twitter, Snapchat, Reddit. + +See [First-Party Guide](/docs/guides/first-party) for details. + +### Partytown Support + +Load scripts off the main thread using web workers. + +```ts +export default defineNuxtConfig({ + modules: ['@nuxtjs/partytown', '@nuxt/scripts'], + scripts: { + partytown: ['plausible', 'fathom', 'umami'], + registry: { + plausible: { domain: 'example.com' } + } + } +}) +``` + +Forward arrays are auto-configured for supported scripts. + +::callout{icon="i-heroicons-exclamation-triangle" color="amber"} +GA4 has [known issues](https://github.com/BuilderIO/partytown/issues/583) with Partytown. GTM is not compatible. Consider Plausible, Fathom, or Umami instead. +:: + +### SSR Social Embeds + +Render X, Instagram, and Bluesky embeds server-side without loading third-party JavaScript. + +Enable the embeds you need in your `nuxt.config`: + +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + scripts: { + registry: { + xEmbed: true, + instagramEmbed: true, + blueskyEmbed: true, + }, + }, +}) +``` + +```vue + + + + + + + +``` + +- Zero third-party JavaScript +- No cookies set by X/Instagram +- User IPs not shared +- All content served from your domain + +### Script Reload + +Re-execute DOM-scanning scripts after SPA navigation: + +```ts +const script = useScript('/third-party.js') +await script.reload() +``` + +### SRI Integrity Hashes + +```ts +export default defineNuxtConfig({ + scripts: { + assets: { + integrity: 'sha384' // or 'sha256', 'sha512' + } + } +}) +``` + +### GTM Default Consent + +```ts +export default defineNuxtConfig({ + scripts: { + registry: { + googleTagManager: { + id: 'GTM-XXXX', + defaultConsent: { + ad_storage: 'denied', + analytics_storage: 'denied' + } + } + } + } +}) +``` + +### New Registry Scripts + +- **PostHog**: Product analytics with feature flags +- **Google reCAPTCHA v3**: Invisible bot protection +- **TikTok Pixel**: Conversion tracking +- **Google Sign-In**: One-tap authentication + +### Google Maps Color Mode + +```vue + +``` + +Auto-switches with `@nuxtjs/color-mode` or manual `color-mode` prop. + +## Breaking Changes + +### YouTube Player + +#### Aspect Ratio + +Use `ratio` prop instead of deriving from `width/height`: + +```diff + +``` + +Default is `16/9`. + +#### Placeholder Image + +Default `object-fit` changed from `contain` to `cover`: + +```vue + +``` + +#### Multiple Players + +Player instances are now properly isolated. Remove any workarounds for multiple players. + +### Google Tag Manager + +#### onBeforeGtmStart Callback + +Now fires for cached/pre-initialized scripts. Guard against multiple calls: + +```ts +let initialized = false +useScriptGoogleTagManager({ + onBeforeGtmStart: (gtag) => { + if (initialized) + return + initialized = true + // your init code + } +}) +``` + +### Type Augmentation + +Templates reorganized. Run `nuxi prepare` after upgrading. diff --git a/docs/content/docs/3.api/_dir.yml b/docs/content/docs/3.api/.navigation.yml similarity index 100% rename from docs/content/docs/3.api/_dir.yml rename to docs/content/docs/3.api/.navigation.yml diff --git a/docs/content/docs/3.api/1.use-script.md b/docs/content/docs/3.api/1.use-script.md index 3eb29008..76237405 100644 --- a/docs/content/docs/3.api/1.use-script.md +++ b/docs/content/docs/3.api/1.use-script.md @@ -1,14 +1,20 @@ --- -title: useScript + +title: useScript() description: API documentation for the useScript function. links: - label: Source icon: i-simple-icons-github to: https://github.com/nuxt/scripts/blob/main/src/runtime/composables/useScript.ts size: xs + --- -This composable is a wrapper around the Unhead [useScript](https://unhead.unjs.io/usage/composables/use-script) with extra Nuxt goodies on top, for +::callout{icon="i-heroicons-play" to="https://stackblitz.com/github/nuxt/scripts/tree/main/examples/custom-script" target="_blank"} +Try the live [Custom Script Example](https://stackblitz.com/github/nuxt/scripts/tree/main/examples/custom-script) on [StackBlitz](https://stackblitz.com). +:: + +This composable is a wrapper around the Unhead [`useScript()`{lang="ts"}](https://unhead.unjs.io/usage/composables/use-script){lang="ts"} with extra Nuxt goodies on top, for full documentation please refer to this. ## Signature @@ -67,6 +73,14 @@ export type NuxtUseScriptOptions = Omit, 'trigger'> * - `false` - Do not bundle the script. (default) */ bundle?: boolean + /** + * [Experimental] Load the script in a web worker using Partytown. + * When enabled, adds `type="text/partytown"` to the script tag. + * Requires @nuxtjs/partytown to be installed and configured separately. + * Note: Scripts requiring DOM access (GTM, Hotjar, chat widgets) are not compatible. + * @see https://partytown.qwik.dev/ + */ + partytown?: boolean /** * Skip any schema validation for the script input. This is useful for loading the script stubs for development without * loading the actual script and not getting warnings. @@ -78,3 +92,25 @@ export type NuxtUseScriptOptions = Omit, 'trigger'> ## Return See the [Understanding proxied functions](/docs/guides/key-concepts) and [$script](https://unhead.unjs.io/usage/composables/use-script#argument-use-script-options) documentation for more information on the return. + +The returned object includes: + +- `status` - Reactive ref with the script status: `'awaitingLoad'` | `'loading'` | `'loaded'` | `'error'` +- `load()`{lang="ts"} - Function to manually load the script +- `remove()`{lang="ts"} - Function to remove the script from the DOM +- `reload()`{lang="ts"} - Function to remove and reload the script (see below) + +### `reload()`{lang="ts"} + +Removes the script and reloads it, forcing re-execution. Useful for third-party scripts that scan the DOM once on load and need to re-run after SPA navigation. + +```ts +const { reload } = useScript('https://example.com/dom-scanner.js') + +// Reload when navigating +watch(() => route.path, () => reload()) +``` + +::callout{icon="i-heroicons-light-bulb" color="blue"} +Many third-party scripts have their own SPA support (e.g., `_iub.cs.api.activateSnippets()`{lang="ts"} for iubenda). Check the script's documentation before using `reload()`{lang="ts"} - their built-in methods are usually more efficient. +:: diff --git a/docs/content/docs/3.api/3.use-script-trigger-consent.md b/docs/content/docs/3.api/3.use-script-trigger-consent.md index 1553c2b0..92f47e9c 100644 --- a/docs/content/docs/3.api/3.use-script-trigger-consent.md +++ b/docs/content/docs/3.api/3.use-script-trigger-consent.md @@ -1,14 +1,20 @@ --- -title: useScriptTriggerConsent + +title: useScriptTriggerConsent() description: API documentation for the useScriptTriggerConsent function. links: - label: Source icon: i-simple-icons-github to: https://github.com/nuxt/scripts/blob/main/src/runtime/composables/useScriptTriggerConsent.ts size: xs + --- -Load a script once consent has been provided either through a resolvable `consent` or calling the `accept` method. +Load a script once you provide consent through either a resolvable `consent` or the `accept` method. + +::callout +The trigger by itself does nothing - scripts will only load when consent is granted through either the `accept()`{lang="ts"} method or by providing a `consent` option that resolves to `true`. +:: ## Signature @@ -35,7 +41,7 @@ export interface ConsentScriptTriggerOptions { ## Returns -A extended promise api with an `accept` method to accept the consent and load the script. +A extended promise API with an `accept` method to accept the consent and load the script. ```ts interface UseConsentScriptTriggerApi extends Promise { @@ -46,7 +52,9 @@ interface UseConsentScriptTriggerApi extends Promise { } ``` -## Example +## Examples + +### Basic Usage ```vue [app.vue] + + +``` + ## Examples diff --git a/docs/content/docs/3.api/3.use-script-trigger-idle-timeout.md b/docs/content/docs/3.api/3.use-script-trigger-idle-timeout.md new file mode 100644 index 00000000..90af193d --- /dev/null +++ b/docs/content/docs/3.api/3.use-script-trigger-idle-timeout.md @@ -0,0 +1,134 @@ +--- + +title: useScriptTriggerIdleTimeout() +description: API documentation for the useScriptTriggerIdleTimeout function. +links: + - label: Source + icon: i-simple-icons-github + to: https://github.com/nuxt/scripts/blob/main/src/runtime/composables/useScriptTriggerIdleTimeout.ts + size: xs + +--- + +Create a trigger that loads a script after an idle timeout once Nuxt is ready. This is useful for non-critical scripts that should load with a delay to further minimize impact on initial page performance. + +## Signature + +```ts +function useScriptTriggerIdleTimeout(options: IdleTimeoutScriptTriggerOptions): Promise +``` + +## Arguments + +```ts +export interface IdleTimeoutScriptTriggerOptions { + /** + * The timeout in milliseconds to wait before loading the script. + */ + timeout: number +} +``` + +## Returns + +A promise that resolves to `true` when the timeout completes and the script should load, or `false` if the trigger is cancelled. + +## Nuxt Config Usage + +For convenience, you can use this trigger directly in your `nuxt.config` without calling the composable explicitly: + +::code-group + +```ts [Registry Script] +export default defineNuxtConfig({ + scripts: { + registry: { + googleAnalytics: [{ + id: 'GA_MEASUREMENT_ID' + }, { + trigger: { idleTimeout: 3000 } // Load 3 seconds after Nuxt ready + }] + } + } +}) +``` + +```ts [Global Script] +export default defineNuxtConfig({ + scripts: { + globals: { + chatWidget: ['https://widget.example.com/chat.js', { + trigger: { idleTimeout: 5000 } // Load 5 seconds after Nuxt ready + }] + } + } +}) +``` + +:: + +## Examples + +### Basic Usage + +Load a script 5 seconds after Nuxt is ready: + +```ts +const script = useScript({ + src: 'https://example.com/analytics.js', +}, { + trigger: useScriptTriggerIdleTimeout({ timeout: 5000 }) +}) +``` + +### Delayed Analytics Loading + +Perfect for analytics scripts that don't need to load immediately: + +```vue + +``` + +### Progressive Enhancement + +Load enhancement scripts with a delay to prioritize critical resources: + +```vue + +``` + +## Best Practices + +- **Use appropriate timeouts**: 3-5 seconds for analytics, 5-10 seconds for widgets, longer for non-essential features +- **Consider user behavior**: Shorter timeouts for high-engagement pages, longer for content-focused pages +- **Monitor performance**: Ensure delayed loading improves your Core Web Vitals +- **Combine with other triggers**: Use alongside interaction triggers for optimal user experience diff --git a/docs/content/docs/3.api/3.use-script-trigger-interaction.md b/docs/content/docs/3.api/3.use-script-trigger-interaction.md new file mode 100644 index 00000000..26fe7095 --- /dev/null +++ b/docs/content/docs/3.api/3.use-script-trigger-interaction.md @@ -0,0 +1,203 @@ +--- + +title: useScriptTriggerInteraction() +description: API documentation for the useScriptTriggerInteraction function. +links: + - label: Source + icon: i-simple-icons-github + to: https://github.com/nuxt/scripts/blob/main/src/runtime/composables/useScriptTriggerInteraction.ts + size: xs + +--- + +Create a trigger that loads a script when any of the specified user interaction events occur. This is perfect for loading scripts only when users interact with your site, providing excellent performance optimization. + +## Signature + +```ts +function useScriptTriggerInteraction(options: InteractionScriptTriggerOptions): Promise +``` + +## Arguments + +```ts +export interface InteractionScriptTriggerOptions { + /** + * The interaction events to listen for. + */ + events: string[] + /** + * The element to listen for events on. + * @default document.documentElement + */ + target?: EventTarget | null +} +``` + +## Returns + +A promise that resolves to `true` when any of the specified interaction events occur and the script should load, or `false` if the trigger is cancelled. + +## Nuxt Config Usage + +For convenience, you can use this trigger directly in your `nuxt.config` without calling the composable explicitly: + +::code-group + +```ts [Registry Script] +export default defineNuxtConfig({ + scripts: { + registry: { + googleAnalytics: [{ + id: 'GA_MEASUREMENT_ID' + }, { + trigger: { interaction: ['scroll', 'click', 'keydown'] } + }] + } + } +}) +``` + +```ts [Global Script] +export default defineNuxtConfig({ + scripts: { + globals: { + chatWidget: ['https://widget.example.com/chat.js', { + trigger: { interaction: ['scroll', 'click', 'touchstart'] } + }] + } + } +}) +``` + +:: + +## Common Event Types + +Here are some commonly used interaction events: + +- **Mouse events**: `click`, `mousedown`, `mouseup`, `mouseover`, `mouseenter`, `mouseleave` +- **Touch events**: `touchstart`, `touchend`, `touchmove` +- **Keyboard events**: `keydown`, `keyup`, `keypress` +- **Scroll events**: `scroll`, `wheel` +- **Focus events**: `focus`, `blur`, `focusin`, `focusout` + +## Examples + +### Basic Usage + +Load a script when the user scrolls, clicks, or presses a key: + +```ts +const script = useScript({ + src: 'https://example.com/chat-widget.js', +}, { + trigger: useScriptTriggerInteraction({ + events: ['scroll', 'click', 'keydown'] + }) +}) +``` + +### Analytics on First Interaction + +Load analytics only when users interact with your site: + +```vue + +``` + +### Specific Element Targeting + +Listen for events on a specific element: + +```vue + + + +``` + +### Progressive Feature Loading + +Load features based on different interaction types: + +```vue + +``` + +### Mobile-Optimized Loading + +Use touch events for mobile-specific optimizations: + +```vue + +``` + +## Best Practices + +- **Start with common events**: Use `['scroll', 'click', 'keydown']` as a good default for most use cases +- **Consider mobile users**: Include touch events like `touchstart` for mobile optimization +- **Target specific elements**: Use the `target` option to listen only on relevant parts of your page +- **Combine events wisely**: Don't overload with too many event types - focus on meaningful interactions +- **Test performance impact**: Verify that interaction-based loading improves your metrics +- **Fallback considerations**: Consider what happens if users don't interact (e.g., combine with a timeout trigger) diff --git a/docs/content/docs/3.api/4.use-script-event-page.md b/docs/content/docs/3.api/4.use-script-event-page.md index d63cffdf..685f1777 100644 --- a/docs/content/docs/3.api/4.use-script-event-page.md +++ b/docs/content/docs/3.api/4.use-script-event-page.md @@ -1,11 +1,13 @@ --- -title: useScriptEventPage + +title: useScriptEventPage() description: API documentation for the useScriptEventPage function. links: - label: Source icon: i-simple-icons-github to: https://github.com/nuxt/scripts/blob/main/src/runtime/composables/useScriptEventPage.ts size: xs + --- Access the current page title and path and trigger an event when they change. @@ -18,7 +20,7 @@ function useScriptEventPage(callback?: (title: string, path: string) => void): R #### Arguments -- `callback` (optional) - A function that will be called when the page title or path changes. +- `callback` (optional) - A function that Nuxt calls when the page title or path changes. #### Returns diff --git a/docs/content/docs/3.api/5.nuxt-config.md b/docs/content/docs/3.api/5.nuxt-config.md index 206f198d..df387434 100644 --- a/docs/content/docs/3.api/5.nuxt-config.md +++ b/docs/content/docs/3.api/5.nuxt-config.md @@ -1,51 +1,131 @@ --- + title: Nuxt Config description: Configure Nuxt Scripts using your Nuxt Config. + --- -## `registry` +## `registry`{lang="ts"} -- Type: `ScriptRegistry` +- Type: `ScriptRegistry`{lang="ts"} -Global registry scripts that should be loaded. +Global registry scripts to load. See the [Script Registry](/scripts) for more details. -## `defaultScriptOptions` +## `partytown`{lang="ts"} :badge[Experimental]{color="amber"} + +- Type: `(keyof ScriptRegistry)[]`{lang="ts"} +- Default: `[]` -- Type: `NuxtUseScriptOptions` +Registry scripts to load via [Partytown](https://partytown.qwik.dev/) (web worker). + +Listing a script here loads it with `type="text/partytown"` so Partytown can execute it in a web worker. + +::code-group +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + scripts: { + partytown: ['googleAnalytics', 'plausible', 'fathom'], + registry: { + googleAnalytics: { id: 'G-XXXXX' }, + plausible: { domain: 'example.com' }, + fathom: { site: 'XXXXX' } + } + } +}) +``` +:: + +::callout{icon="i-heroicons-exclamation-triangle" color="amber"} +Requires you to install [`@nuxtjs/partytown`](https://github.com/nuxt-modules/partytown). Nuxt automatically configures the `forward` array for supported registry scripts. +:: + +### Supported Scripts + +Scripts with auto-forwarding configured: +- `googleAnalytics`, `plausible`, `fathom`, `umami`, `matomo`, `segment` +- `metaPixel`, `xPixel`, `tiktokPixel`, `snapchatPixel`, `redditPixel` +- `cloudflareWebAnalytics` + +### Limitations + +::callout{icon="i-heroicons-x-circle" color="red"} +**Incompatible scripts** - The following cannot work with Partytown due to DOM access requirements: +- **Tag managers**: Google Tag Manager, Adobe Launch +- **Session replay**: Hotjar, Clarity, FullStory, LogRocket +- **Chat widgets**: Intercom, Crisp, Drift, HubSpot Chat +- **Video players**: [YouTube](https://youtube.com), Vimeo embeds +- **A/B testing**: Optimizely, VWO, Google Optimize +:: + +::callout{icon="i-heroicons-exclamation-triangle" color="amber"} +**General limitations**: +- Scripts cannot directly access or modify the DOM +- `document.cookie` access requires additional Partytown configuration +- Debugging is more complex (code runs in a web worker) +- Some scripts may have timing differences compared to main thread execution +- localStorage/sessionStorage access may require forwarding configuration +:: + +## `defaultScriptOptions`{lang="ts"} + +- Type: `NuxtUseScriptOptions`{lang="ts"} - Default: `{ trigger: 'onNuxtReady' }` -Default options for scripts. See the [useScript](/docs/api/use-script) documentation for more details. +Default options for scripts. See the [`useScript()`{lang="ts"}](/docs/api/use-script){lang="ts"} documentation for more details. -## `globals` +## `globals`{lang="ts"} -- Type: `(NuxtUseScriptInput | [NuxtUseScriptInput, NuxtUseScriptOptions])[]` +- Type: `(NuxtUseScriptInput | [NuxtUseScriptInput, NuxtUseScriptOptions])[]`{lang="ts"} - Default: `[]` -Global scripts that should be loaded on all pages. This is a configuration for the [useScript](/docs/api/use-script) composable. +Global scripts to load on all pages. This configuration applies to the [`useScript()`{lang="ts"}](/docs/api/use-script){lang="ts"} composable. See the [Globals](/docs/guides/global) documentation for more details. -## `assets` +## `enabled`{lang="ts"} + +- Type: `boolean`{lang="ts"} +- Default: `true` + +Disables the Nuxt Scripts module. + +## `debug`{lang="ts"} + +- Type: `boolean`{lang="ts"} +- Default: `false` + +Enable to see debug logs. + +## `assets`{lang="ts"} -- Type: `object` +- Type: `object`{lang="ts"} - Default: `{ prefix: '/_scripts/', strategy: 'public' }` -Controls the way scripts are bundled to be served by Nuxt. +Controls how Nuxt bundles scripts for serving. See the [Bundling](/docs/guides/bundling) documentation for more details. -## `enabled` +## `assets.fallbackOnSrcOnBundleFail`{lang="ts"} -- Type: `boolean` -- Default: `true` +- Type: `boolean`{lang="ts"} +- Default: `false` -Disables the Nuxt Scripts module. +Fallback to the remote src URL when `bundle` fails when enabled. By default, the bundling process stops if the third-party script can't be downloaded. -## `debug` +## `assets.fetchOptions`{lang="ts"} -- Type: `boolean` +- Type: `object`{lang="ts"} +- Default: `{ retry: 3, retryDelay: 2000, timeout: 15_000 }` + +Options to pass to the fetch function when downloading scripts. + +## `assets.integrity`{lang="ts"} + +- Type: `boolean | 'sha256' | 'sha384' | 'sha512'`{lang="ts"} - Default: `false` -Enable to see debug logs. +Enable automatic Subresource Integrity (SRI) hash generation for bundled scripts. When enabled, calculates a cryptographic hash of each bundled script and injects the `integrity` attribute along with `crossorigin="anonymous"`. + +See the [Bundling - Subresource Integrity](/docs/guides/bundling#subresource-integrity-sri) documentation for more details. diff --git a/docs/content/docs/3.api/6.nuxt-app-hooks.md b/docs/content/docs/3.api/6.nuxt-app-hooks.md index f878e40c..701eb1bf 100644 --- a/docs/content/docs/3.api/6.nuxt-app-hooks.md +++ b/docs/content/docs/3.api/6.nuxt-app-hooks.md @@ -1,15 +1,17 @@ --- + title: Nuxt App Hooks description: Use Nuxt App hooks to extend the Nuxt Scripts runtime behavior. + --- -## `scripts:updated` +## `scripts:updated`{lang="ts"} -- Type: `async (ctx: { scripts: ScriptRegistry }) => HookResult` +- Type: `async (ctx: { scripts: ScriptRegistry }) => HookResult`{lang="ts"} -Triggered after the script status is updated. +Triggered after Nuxt updates the script status. -This is used internally for the DevTools but can be used however you see fit. +Nuxt uses this internally for the DevTools, but you can use it however you see fit. ```ts [plugins/nuxt-scripts.ts] export default defineNuxtPlugin({ @@ -21,13 +23,13 @@ export default defineNuxtPlugin({ }) ``` -## `script:instance-fn` +## `script:instance-fn`{lang="ts"} -- Type: `(ctx: { script: ScriptInstance, fn: string | symbol, args: any, exists: boolean }) => HookResult` +- Type: `(ctx: { script: ScriptInstance, fn: string | symbol, args: any, exists: boolean }) => HookResult`{lang="ts"} This is exposed only from Unhead, it's fired when accessing properties via the proxy instance. -This is also used internally for the DevTools but can be used however you see fit. +Nuxt also uses this internally for the DevTools, but you can use it however you see fit. ```ts export default defineNuxtPlugin({ diff --git a/docs/content/docs/3.api/6.nuxt-hooks.md b/docs/content/docs/3.api/6.nuxt-hooks.md index bc312cc2..58ef151b 100644 --- a/docs/content/docs/3.api/6.nuxt-hooks.md +++ b/docs/content/docs/3.api/6.nuxt-hooks.md @@ -1,21 +1,23 @@ --- + title: Nuxt Hooks description: Use Nuxt hooks to extend the Nuxt Scripts module. + --- -## `scripts:registry` +## `scripts:registry`{lang="ts"} -- Type: `async (ctx: { registry: ScriptRegistry }) => HookResult` +- Type: `async (registry: RegistryScripts) => HookResult`{lang="ts"} -Add registry scripts at build, allowing them to be loaded via `scripts.registry` and bundled if available. +Add registry scripts at build, allowing you to load them via `scripts.registry` and bundle them if available. -This is intended to be used by modules. +Modules should use this hook. ```ts [module.ts] export default defineNuxtModule({ setup() { - nuxt.hooks.hook('scripts:registry', async (ctx) => { - ctx.registry.add({ + nuxt.hooks.hook('scripts:registry', async (registry) => { + registry.push({ // used in DevTools label: 'My Custom Script', logo: ``, diff --git a/docs/content/scripts/.navigation.yml b/docs/content/scripts/.navigation.yml new file mode 100644 index 00000000..fc997444 --- /dev/null +++ b/docs/content/scripts/.navigation.yml @@ -0,0 +1 @@ +title: Script Registry diff --git a/docs/content/scripts/_dir.yml b/docs/content/scripts/_dir.yml deleted file mode 100644 index 94164c7e..00000000 --- a/docs/content/scripts/_dir.yml +++ /dev/null @@ -1,2 +0,0 @@ -icon: i-ph-floppy-disk-duotone -title: Script Registry diff --git a/docs/content/scripts/ads/google-adsense.md b/docs/content/scripts/ads/google-adsense.md deleted file mode 100644 index f083e567..00000000 --- a/docs/content/scripts/ads/google-adsense.md +++ /dev/null @@ -1,92 +0,0 @@ ---- -title: Google Adsense -description: Show Google Adsense ads in your Nuxt app. -links: - - label: useScriptGoogleAdsense - icon: i-simple-icons-github - to: https://github.com/nuxt/scripts/blob/main/src/runtime/registry/google-adsense.ts - size: xs - - label: "" - icon: i-simple-icons-github - to: https://github.com/nuxt/scripts/blob/main/src/runtime/components/ScriptGoogleAdsense.vue - size: xs ---- - -:UAlert{title="Experimental" description="The Google Adsense integration has not been fully tested, use with caution." color="yellow" variant="soft" class="not-prose"} - -[Google Adsense](https://www.google.com/adsense/start/) allows you to monetize your website by displaying ads. - -Nuxt Scripts provides a `useScriptGoogleAdsense` composable and a headless `ScriptGoogleAdsense` component to interact with the Google Adsense. - -## ScriptGoogleAdsense - -The `ScriptGoogleAdsense` component is a wrapper around the `useScriptGoogleAdsense` composable. It provides a simple way to embed ads in your Nuxt app. - -```vue - -``` - -### Handling Ad-blockers - -You can use these hooks to add a fallback when the Google Adsense script is blocked. - -```vue - -``` - - -### Component API - -See the [Facade Component API](/docs/guides/facade-components#facade-components-api) for full props, events, and slots. - -### Props - -The `ScriptGoogleAdsense` component supports all props that Google Adsense supports on the `` tag. See the [Ad tags documentation](https://developers.google.com/adsense/platforms/transparent/ad-tags) for more information. - -At a minimum you must provide the following tags: -- `data-ad-client`: The Google Adsense ID. -- `data-ad-slot`: The slot ID. - -## useScriptGoogleAdsense - -The `useScriptGoogleAdsense` composable lets you have fine-grain control over the Google Adsense script. - -```ts -export function useScriptGoogleAdsense(_options?: GoogleAdsenseInput) {} -``` - -Please follow the [Registry Scripts](/docs/guides/registry-scripts) guide to learn more about advanced usage. - -### GoogleAdsenseApi - -```ts -export interface GoogleAdsenseApi { - adsbygoogle: any[] & { loaded: boolean } -} -``` - -### GoogleAdsenseInput - -```ts -export const GoogleAdsenseOptions = object({ - /** - * The Google Adsense ID. - */ - client: optional(string()), -}) -``` diff --git a/docs/content/scripts/analytics/cloudflare-web-analytics.md b/docs/content/scripts/analytics/cloudflare-web-analytics.md deleted file mode 100644 index c63625d0..00000000 --- a/docs/content/scripts/analytics/cloudflare-web-analytics.md +++ /dev/null @@ -1,138 +0,0 @@ ---- -title: Cloudflare Web Analytics -description: Use Cloudflare Web Analytics in your Nuxt app. -links: - - label: Source - icon: i-simple-icons-github - to: https://github.com/nuxt/scripts/blob/main/src/runtime/registry/cloudflare-web-analytics.ts - size: xs ---- - -[Cloudflare Web Analytics](https://developers.cloudflare.com/analytics/web-analytics/) with Nuxt is a great privacy analytics solution. It offers free, privacy-centric analytics for your website. It doesn't gather personal data from your visitors, yet provides detailed insights into your web pages' performance as experienced by your visitors. - -The simplest way to load Cloudflare Web Analytics globally in your Nuxt App is to use Nuxt config. Alternatively you can directly -use the [useScriptCloudflareWebAnalytics](#usescriptcloudflarewebanalytics) composable. - -## Loading Globally - -If you'd like to avoid loading the analytics in development, you can use the [Environment overrides](https://nuxt.com/docs/getting-started/configuration#environment-overrides) in your Nuxt config. - -::code-group - -```ts [Always enabled] -export default defineNuxtConfig({ - scripts: { - registry: { - cloudflareWebAnalytics: { - token: 'YOUR_TOKEN_ID' - } - } - } -}) -``` - -```ts [Production only] -export default defineNuxtConfig({ - $production: { - scripts: { - registry: { - cloudflareWebAnalytics: { - token: 'YOUR_TOKEN_ID', - } - } - } - } -}) -``` - -```ts [Environment Variables] -export default defineNuxtConfig({ - scripts: { - registry: { - cloudflareWebAnalytics: true, - } - }, - // you need to provide a runtime config to access the environment variables - runtimeConfig: { - public: { - scripts: { - cloudflareWebAnalytics: { - // .env - // NUXT_PUBLIC_SCRIPTS_CLOUDFLARE_WEB_ANALYTICS_TOKEN= - token: '', - }, - }, - }, - }, -}) -``` - -:: - -## useScriptCloudflareWebAnalytics - -The `useScriptCloudflareWebAnalytics` composable lets you have fine-grain control over when and how Cloudflare Web Analytics is loaded on your site. - -```ts -function useScriptCloudflareWebAnalytics(_options?: CloudflareWebAnalyticsInput) {} -``` - -Please follow the [Registry Scripts](/docs/guides/registry-scripts) guide to learn more about advanced usage. - -The composable comes with the following defaults: -- **Trigger: Client** Script will load when the Nuxt is hydrating to keep web vital metrics accurate. - -### CloudflareWebAnalyticsInput - -```ts -export const CloudflareWebAnalyticsOptions = object({ - /** - * The Cloudflare Web Analytics token. - */ - token: string([minLength(32)]), - /** - * Cloudflare Web Analytics enables measuring SPAs automatically by overriding the History API’s pushState function - * and listening to the onpopstate. Hash-based router is not supported. - * - * @default true - */ - spa: optional(boolean()), -}) -``` - -### CloudflareWebAnalyticsApi - -```ts -export interface CloudflareWebAnalyticsApi { - __cfBeacon: { - load: 'single' - spa: boolean - token: string - } -} -``` - -## Example - -Loading Cloudflare Web Analytics through the `app.vue` when Nuxt is ready. - -```vue [app.vue] - -``` - -The Cloudflare Web Analytics composable injects a `window.__cfBeacon` object into the global scope. If you need -to access this you can do by awaiting the script. - -```ts -const { onLoaded } = useScriptCloudflareWebAnalytics() -onLoaded(({ cfBeacon }) => { - console.log(cfBeacon) -}) -``` diff --git a/docs/content/scripts/analytics/fathom-analytics.md b/docs/content/scripts/analytics/fathom-analytics.md deleted file mode 100644 index ee7924d2..00000000 --- a/docs/content/scripts/analytics/fathom-analytics.md +++ /dev/null @@ -1,168 +0,0 @@ ---- -title: Fathom Analytics -description: Use Fathom Analytics in your Nuxt app. -links: - - label: Source - icon: i-simple-icons-github - to: https://github.com/nuxt/scripts/blob/main/src/runtime/registry/fathom-analytics.ts - size: xs ---- - -[Fathom Analytics](https://usefathom.com/) is a great privacy analytics solution for your Nuxt app. It doesn't gather personal data from your visitors, yet provides detailed insights into how your site is used. - -## Loading Globally - -The simplest way to load Fathom Analytics globally in your Nuxt App is to use your Nuxt config, providing your site ID -as a string. - -::code-group - -```ts [Always enabled] -export default defineNuxtConfig({ - scripts: { - registry: { - fathomAnalytics: { - site: 'YOUR_TOKEN_ID' - } - } - } -}) -``` - -```ts [Production only] -export default defineNuxtConfig({ - $production: { - scripts: { - registry: { - fathomAnalytics: { - site: 'YOUR_SITE_ID' - } - } - } - }, -}) -``` - - -```ts [Environment Variables] -export default defineNuxtConfig({ - scripts: { - registry: { - fathomAnalytics: true, - } - }, - // you need to provide a runtime config to access the environment variables - runtimeConfig: { - public: { - scripts: { - fathomAnalytics: { - // .env - // NUXT_PUBLIC_SCRIPTS_FATHOM_ANALYTICS_SITE= - token: '', - }, - }, - }, - }, -}) -``` - -:: - -## Composable `useScriptFathomAnalytics` - -The `useScriptFathomAnalytics` composable lets you have fine-grain control over when and how Fathom Analytics is loaded on your site. - -```ts -useScriptFathomAnalytics(options) -``` - -## Defaults - -- **Trigger**: Script will load when Nuxt is hydrated. - -## Options - -```ts -export const FathomAnalyticsOptions = object({ - /** - * The Fathom Analytics site ID. - */ - site: string(), - /** - * The Fathom Analytics tracking mode. - */ - spa: optional(union([literal('auto'), literal('history'), literal('hash')])), - /** - * Automatically track page views. - */ - auto: optional(boolean()), - /** - * Enable canonical URL tracking. - */ - canonical: optional(boolean()), - /** - * Honor Do Not Track requests. - */ - honorDnt: optional(boolean()), -}) -``` - -Additionally like all registry scripts you can provide extra configuration: - -- `scriptInput` - HTML attributes to add to the script tag. -- `scriptOptions` - See [Script Options]. Bundling is not supported, `bundle: true` will not do anything. - -## Return values - -The Fathom Analytics composable injects a `window.fathom` object into the global scope. - -```ts -export interface FathomAnalyticsApi { - beacon: (ctx: { url: string, referrer?: string }) => void - blockTrackingForMe: () => void - enableTrackingForMe: () => void - isTrackingEnabled: () => boolean - send: (type: string, data: unknown) => void - setSite: (siteId: string) => void - sideId: string - trackPageview: (ctx?: { url: string, referrer?: string }) => void - trackGoal: (goalId: string, cents: number) => void - trackEvent: (eventName: string, value: { _value: number }) => void -} -``` - -You can access the `fathom` object as a proxy directly or await the `$script` promise to access the object. It's recommended -to use the proxy for any void functions. - -::code-group - -```ts [Proxy] -const { proxy } = useScriptFathomAnalytics() -function trackMyGoal() { - proxy.trackGoal('MY_GOAL_ID', 100) -} -``` - -```ts [onLoaded] -const { onLoaded } = useScriptFathomAnalytics() -onLoaded(({ trackGoal }) => { - trackGoal('MY_GOAL_ID', 100) -}) -``` - -:: - -## Example - -Loading Fathom Analytics through the `app.vue` when Nuxt is ready. - -```vue [app.vue] - -``` diff --git a/docs/content/scripts/analytics/google-analytics.md b/docs/content/scripts/analytics/google-analytics.md deleted file mode 100644 index 0096c5cc..00000000 --- a/docs/content/scripts/analytics/google-analytics.md +++ /dev/null @@ -1,162 +0,0 @@ ---- -title: Google Analytics -description: Use Google Analytics in your Nuxt app. -links: - - label: Source - icon: i-simple-icons-github - to: https://github.com/nuxt/scripts/blob/main/src/runtime/registry/google-analytics.ts - size: xs ---- - -::tip -This composable is generated with [GoogleChromeLabs/third-party-capital](https://github.com/GoogleChromeLabs/third-party-capital) in collaboration with the [Chrome Aurora team](https://developer.chrome.com/docs/aurora). -:: - -[Google Analytics](https://marketingplatform.google.com/about/analytics/) is an analytics solution for Nuxt Apps. - -It provides detailed insights into how your website is performing, how users are interacting with your content, and how they are navigating through your site. - -The simplest way to load Google Analytics globally in your Nuxt App is to use Nuxt config. Alternatively you can directly -use the [useScriptGoogleAnalytics](#useScriptGoogleAnalytics) composable. - -### Loading Globally - -If you don't plan to send custom events you can use the [Environment overrides](https://nuxt.com/docs/getting-started/configuration#environment-overrides) to -disable the script in development. - -::code-group - -```ts [Always enabled] -export default defineNuxtConfig({ - scripts: { - registry: { - googleAnalytics: { - id: 'YOUR_ID', - } - } - } -}) -``` - -```ts [Production only] -export default defineNuxtConfig({ - $production: { - scripts: { - registry: { - googleAnalytics: { - id: 'YOUR_ID', - } - } - } - } -}) -``` - -```ts [Environment Variables] -export default defineNuxtConfig({ - scripts: { - registry: { - googleAnalytics: true, - } - }, - // you need to provide a runtime config to access the environment variables - runtimeConfig: { - public: { - scripts: { - googleAnalytics: { - // .env - // NUXT_PUBLIC_SCRIPTS_GOOGLE_ANALYTICS_ID= - id: '', - }, - }, - }, - }, -}) -``` - -:: - -## useScriptGoogleAnalytics - -The `useScriptGoogleAnalytics` composable lets you have fine-grain control over when and how Google Analytics is loaded on your site. - -```ts -const googleAnalytics = useScriptGoogleAnalytics({ - id: 'YOUR_ID' -}) -``` - -Please follow the [Registry Scripts](/docs/guides/registry-scripts) guide to learn more about advanced usage. - -### GoogleAnalyticsApi - -```ts -interface GTag { - (fn: 'js', opt: Date): void - (fn: 'config', opt: string): void - (fn: 'event', opt: string, opt2?: { - [key: string]: any - }): void - (fn: 'set', opt: { - [key: string]: string - }): void - (fn: 'get', opt: string): void - (fn: 'consent', opt: 'default', opt2: { - [key: string]: string - }): void - (fn: 'consent', opt: 'update', opt2: { - [key: string]: string - }): void - (fn: 'config', opt: 'reset'): void -} -interface GoogleAnalyticsApi { - dataLayer: Record[] - gtag: GTag -} -``` - -### Config Schema - -You must provide the options when setting up the script for the first time. - -```ts -export const GoogleAnalyticsOptions = object({ - /** - * The Google Analytics ID. - */ - id: string(), - /** - * The datalayer's name you want it to be associated with - */ - dataLayerName: optional(string()) -}) -``` - -## Example - -Using Google Analytics only in production while using `gtag` to send a conversion event. - -::code-group - -```vue [ConversionButton.vue] - - - -``` - -:: diff --git a/docs/content/scripts/analytics/matomo-analytics.md b/docs/content/scripts/analytics/matomo-analytics.md deleted file mode 100644 index 8563e61a..00000000 --- a/docs/content/scripts/analytics/matomo-analytics.md +++ /dev/null @@ -1,136 +0,0 @@ ---- -title: Matomo Analytics -description: Use Matomo Analytics in your Nuxt app. -links: - - label: Source - icon: i-simple-icons-github - to: https://github.com/nuxt/scripts/blob/main/src/runtime/registry/matomo-analytics.ts - size: xs ---- - -[Matomo Analytics](https://matomo.org/) is a great analytics solution for Nuxt Apps. - -It provides detailed insights into how your website is performing, how users are interacting with your content, and how they are navigating through your site. - -The simplest way to load Matomo Analytics globally in your Nuxt App is to use Nuxt config. Alternatively you can directly -use the [useScriptMatomoAnalytics](#useScriptMatomoAnalytics) composable. - -## Loading Globally - -If you don't plan to send custom events you can use the [Environment overrides](https://nuxt.com/docs/getting-started/configuration#environment-overrides) to -disable the script in development. - -::code-group - -```ts [Always enabled] -export default defineNuxtConfig({ - scripts: { - registry: { - matomoAnalytics: { - siteId: 'YOUR_SITE_ID' - } - } - } -}) -``` - -```ts [Production only] -export default defineNuxtConfig({ - $production: { - scripts: { - registry: { - matomoAnalytics: { - siteId: 'YOUR_SITE_ID', - } - } - } - } -}) -``` - -```ts [Environment Variables] -export default defineNuxtConfig({ - scripts: { - registry: { - matomoAnalytics: true, - } - }, - // you need to provide a runtime config to access the environment variables - runtimeConfig: { - public: { - scripts: { - matomoAnalytics: { - // .env - // NUXT_PUBLIC_SCRIPTS_MATOMO_ANALYTICS_SITE_ID= - siteId: '', // NUXT_PUBLIC_SCRIPTS_MATOMO_ANALYTICS_SITE_ID - }, - }, - }, - }, -}) -``` - -:: - -## useScriptMatomoAnalytics - -The `useScriptMatomoAnalytics` composable lets you have fine-grain control over when and how Matomo Analytics is loaded on your site. - -```ts -const matomoAnalytics = useScriptMatomoAnalytics({ - matomoUrl: 'YOUR_MATOMO_URL', - siteId: 'YOUR_SITE_ID' -}) -``` - -Please follow the [Registry Scripts](/docs/guides/registry-scripts) guide to learn more about advanced usage. - -### MatomoAnalyticsApi - -```ts -interface MatomoAnalyticsApi { - _paq: unknown[] -} -``` - -### Config Schema - -You must provide the options when setting up the script for the first time. - -```ts -// matomoUrl and site are required -export const MatomoAnalyticsOptions = object({ - matomoUrl: string(), - siteId: string(), - trackPageView: optional(boolean()), - enableLinkTracking: optional(boolean()), -}) -``` - -## Example - -Using Matomo Analytics only in production while using `_paq` to send a conversion event. - -::code-group - -```vue [ConversionButton.vue] - - - -``` - -:: diff --git a/docs/content/scripts/analytics/plausible-analytics.md b/docs/content/scripts/analytics/plausible-analytics.md deleted file mode 100644 index 60edf17c..00000000 --- a/docs/content/scripts/analytics/plausible-analytics.md +++ /dev/null @@ -1,148 +0,0 @@ ---- -title: Plausible Analytics -description: Use Plausible Analytics in your Nuxt app. -links: - - label: Source - icon: i-simple-icons-github - to: https://github.com/nuxt/scripts/blob/main/src/runtime/registry/plausible-analytics.ts - size: xs ---- - -[Plausible Analytics](https://plausible.io/) is a privacy-friendly analytics solution for Nuxt Apps, allowing you to track your website's traffic without compromising your users' privacy. - -The simplest way to load Plausible Analytics globally in your Nuxt App is to use Nuxt config. Alternatively you can directly -use the [useScriptPlausibleAnalytics](#useScriptPlausibleAnalytics) composable. - -## Loading Globally - -If you don't plan to send custom events you can use the [Environment overrides](https://nuxt.com/docs/getting-started/configuration#environment-overrides) to -disable the script in development. - -::code-group - -```ts [Always enabled] -export default defineNuxtConfig({ - scripts: { - registry: { - plausibleAnalytics: { - domain: 'YOUR_DOMAIN' - } - } - } -}) -``` - -```ts [Production only] -export default defineNuxtConfig({ - $production: { - scripts: { - registry: { - plausibleAnalytics: { - domain: 'YOUR_DOMAIN', - } - } - } - } -}) -``` - -```ts [Environment Variables] -export default defineNuxtConfig({ - scripts: { - registry: { - plausibleAnalytics: true, - } - }, - // you need to provide a runtime config to access the environment variables - runtimeConfig: { - public: { - scripts: { - plausibleAnalytics: { - // .env - // NUXT_PUBLIC_SCRIPTS_PLAUSIBLE_ANALYTICS_DOMAIN= - domain: '' - }, - }, - }, - }, -}) -``` - -:: - -## useScriptPlausibleAnalytics - -The `useScriptPlausibleAnalytics` composable lets you have fine-grain control over when and how Plausible Analytics is loaded on your site. - -```ts -const plausible = useScriptPlausibleAnalytics({ - domain: 'YOUR_DOMAIN' -}) -``` - -Please follow the [Registry Scripts](/docs/guides/registry-scripts) guide to learn more about advanced usage. - -### Self-hosted Plausible - -If you are using a self-hosted version of Plausible, you will need to provide an explicit src for the script so that -the API events are sent to the correct endpoint. - -```ts -useScriptPlausible({ - scriptInput: { - src: 'https://my-self-hosted-plausible.io/js/script.js' - } -}) -``` - -### PlausibleAnalyticsApi - -```ts -export interface PlausibleAnalyticsApi { - plausible: ((event: '404', options: Record) => void) & - ((event: 'event', options: Record) => void) & - ((...params: any[]) => void) & { - q: any[] - } -} -``` - -### Config Schema - -You must provide the options when setting up the script for the first time. - -```ts -export const PlausibleAnalyticsOptions = object({ - domain: string(), // required - extension: optional(union([union(extensions), array(union(extensions))])), -}) -``` - -## Example - -Using Plausible Analytics only in production while using `plausible` to send a conversion event. - -::code-group - -```vue [ConversionButton.vue] - - - -``` - -:: diff --git a/docs/content/scripts/bluesky-embed.md b/docs/content/scripts/bluesky-embed.md new file mode 100644 index 00000000..50b8a796 --- /dev/null +++ b/docs/content/scripts/bluesky-embed.md @@ -0,0 +1,287 @@ +--- + +title: Bluesky Embed +description: Server-side rendered Bluesky embeds with zero client-side API calls. +links: + - label: ScriptBlueskyEmbed + icon: i-simple-icons-github + to: https://github.com/nuxt/scripts/blob/main/src/runtime/components/ScriptBlueskyEmbed.vue + size: xs + +--- + +[Bluesky](https://bsky.app) is a decentralized social media platform built on the AT Protocol. + +Nuxt Scripts provides a [``{lang="html"}](/scripts/bluesky-embed){lang="html"} component that fetches post data server-side and exposes it via slots for complete styling control. All data is proxied through your server - no client-side API calls to Bluesky. + +::script-stats +:: + +::script-types +:: + +## Setup + +To use the Bluesky embed component, you must enable it in your `nuxt.config`: + +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + scripts: { + registry: { + blueskyEmbed: true, + }, + }, +}) +``` + +This registers the required server API routes (`/_scripts/embed/bluesky` and `/_scripts/embed/bluesky-image`) that handle fetching post data and proxying images. + +## [``{lang="html"}](/scripts/bluesky-embed){lang="html"} + +The [``{lang="html"}](/scripts/bluesky-embed){lang="html"} component is a headless component that: +- Fetches post data server-side via the Bluesky public API (AT Protocol) +- Proxies all images through your server for privacy +- Converts rich text facets (links, mentions, hashtags) to HTML +- Exposes post data via scoped slots for custom rendering +- Caches responses for 10 minutes +- Respects author opt-out (`!no-unauthenticated` label) + +### Demo + +::code-group + +```vue [Basic Usage] + +``` + +```vue [Bluesky Card (Tailwind)] + +``` + +```vue [Minimal] + +``` + +:: + +### Slot Props + +The default slot receives the following props: + +```ts +interface SlotProps { + // Raw data + post: BlueskyEmbedPostData + // Author info + displayName: string + handle: string + avatar: string // Proxied URL + avatarOriginal: string // Original Bluesky CDN URL + isVerified: boolean + // Post content + text: string // Plain text + richText: string // HTML with links, mentions, and hashtags + langs?: string[] // Language codes + // Formatted values + datetime: string // "12:47 PM · Feb 5, 2024" + createdAt: Date + likes: number + likesFormatted: string // "1.2K" + reposts: number + repostsFormatted: string // "234" + replies: number + repliesFormatted: string // "42" + quotes: number + quotesFormatted: string // "12" + // Media + images?: Array<{ + thumb: string // Proxied thumbnail URL + fullsize: string // Proxied full-size URL + alt: string + aspectRatio?: { width: number, height: number } + }> + externalEmbed?: { + uri: string + title: string + description: string + thumb?: string // Proxied URL + } + // Links + postUrl: string + authorUrl: string + // Helpers + proxyImage: (url: string) => string +} +``` + +### Named Slots + +| Slot | Description | +|------|-------------| +| `default` | Main content with slot props | +| `loading` | Shown while fetching post data | +| `error` | Shown if post fetch fails, receives `{ error }` | + +## How It Works + +1. **Server-side fetch**: The server fetches post data from `public.api.bsky.app` (AT Protocol) during SSR +2. **Handle resolution**: The server resolves handles to DIDs for reliable post lookup +3. **Image proxying**: The server rewrites all images to proxy through `/_scripts/embed/bluesky-image` +4. **Rich text**: The component converts Bluesky facets (links, mentions, hashtags) to HTML +5. **Caching**: The server caches responses for 10 minutes +6. **No client-side API calls**: The user's browser never contacts Bluesky directly + +## Privacy Benefits + +- No third-party JavaScript loaded +- No cookies set by Bluesky +- No direct browser-to-Bluesky communication +- User IP addresses not shared with Bluesky +- All content served from your domain + +## Author Opt-Out + +The component respects Bluesky's `!no-unauthenticated` label. If a post author has opted out of external embedding, the API returns a 403 error and the component shows the error slot. diff --git a/docs/content/scripts/ads/carbon-ads.md b/docs/content/scripts/carbon-ads.md similarity index 80% rename from docs/content/scripts/ads/carbon-ads.md rename to docs/content/scripts/carbon-ads.md index 5294f386..66d54283 100644 --- a/docs/content/scripts/ads/carbon-ads.md +++ b/docs/content/scripts/carbon-ads.md @@ -1,4 +1,5 @@ --- + title: Carbon Ads description: Show carbon ads in your Nuxt app using a Vue component. links: @@ -6,15 +7,22 @@ links: icon: i-simple-icons-github to: https://github.com/nuxt/scripts/blob/main/src/runtime/components/ScriptCarbonAds.vue size: xs + --- [Carbon Ads](https://www.carbonads.net/) is an ad service that provides a performance friendly way to show ads on your site. -Nuxt Scripts provides a headless `ScriptCarbonAds` component to embed Carbon Ads in your Nuxt app. +Nuxt Scripts provides a headless [``{lang="html"}](/scripts/carbon-ads){lang="html"} component to embed Carbon Ads in your Nuxt app. + +::script-stats +:: + +::script-types +:: -## ScriptCarbonAds +## [``{lang="html"}](/scripts/carbon-ads){lang="html"} -The `ScriptCarbonAds` component works differently to other Nuxt Scripts component and does not rely on `useScript`, instead it simply +The [``{lang="html"}](/scripts/carbon-ads){lang="html"} component works differently to other Nuxt Scripts component and does not rely on [`useScript()`{lang="ts"}](/docs/api/use-script){lang="ts"}, instead it simply inserts a script tag into the div of the component on mount. By default, the component uses CarbonAds best practices which is to load immediately on mount. You can make use of [Element Event Triggers](/docs/guides/script-triggers#element-event-triggers) if you @@ -25,6 +33,7 @@ want to load the ads on a specific event. ``` @@ -38,6 +47,7 @@ You can use these hooks to add a fallback when CarbonAds is blocked. @@ -161,12 +172,4 @@ use this example from nuxt.com. See the [Facade Component API](/docs/guides/facade-components#facade-components-api) for full props, events, and slots. -Note: The Carbon Ads script _does not_ extend the `useScript` composable. Accessing the script will return the `HTMLScriptElement`. - -### Props - -The `ScriptCarbonAds` component accepts the following props: - -- `serve`: The serve URL provided by Carbon Ads. -- `placement`: The placement ID provided by Carbon Ads. - +Note: The Carbon Ads script _does not_ extend the [`useScript()`{lang="ts"}](/docs/api/use-script){lang="ts"} composable. Accessing the script will return the `HTMLScriptElement`. diff --git a/docs/content/scripts/clarity.md b/docs/content/scripts/clarity.md new file mode 100644 index 00000000..c4480e29 --- /dev/null +++ b/docs/content/scripts/clarity.md @@ -0,0 +1,22 @@ +--- + +title: Clarity +description: Use Clarity in your Nuxt app. +links: +- label: Source + icon: i-simple-icons-github + to: https://github.com/nuxt/scripts/blob/main/src/runtime/registry/clarity.ts + size: xs + +--- + +[Clarity](https://clarity.microsoft.com/) by Microsoft is a screen recorder and heatmap tool that helps you understand how users interact with your website. + +::script-stats +:: + +::script-docs +:: + +::script-types +:: diff --git a/docs/content/scripts/cloudflare-web-analytics.md b/docs/content/scripts/cloudflare-web-analytics.md new file mode 100644 index 00000000..83cc4997 --- /dev/null +++ b/docs/content/scripts/cloudflare-web-analytics.md @@ -0,0 +1,50 @@ +--- + +title: Cloudflare Web Analytics +description: Use Cloudflare Web Analytics in your Nuxt app. +links: + - label: Source + icon: i-simple-icons-github + to: https://github.com/nuxt/scripts/blob/main/src/runtime/registry/cloudflare-web-analytics.ts + size: xs + +--- + +[Cloudflare Web Analytics](https://developers.cloudflare.com/analytics/web-analytics/) with Nuxt is a great privacy analytics solution. It offers free, privacy-centric analytics for your website. It doesn't gather personal data from your visitors, yet provides detailed insights into your web pages' performance as experienced by your visitors. + +::script-stats +:: + +::script-docs +:: + +The composable comes with the following defaults: +- **Trigger: Client** Script will load when the Nuxt is hydrating to keep web vital metrics accurate. + +::script-types +:: + +## Loading in app.vue + +Loading Cloudflare Web Analytics through the `app.vue` when Nuxt is ready. + +```vue [app.vue] + +``` + +The Cloudflare Web Analytics composable injects a `window.__cfBeacon` object into the global scope. If you need +to access this you can do by awaiting the script. + +```ts +const { onLoaded } = useScriptCloudflareWebAnalytics() +onLoaded(({ cfBeacon }) => { + console.log(cfBeacon) +}) +``` diff --git a/docs/content/scripts/content/google-maps.md b/docs/content/scripts/content/google-maps.md deleted file mode 100644 index 17b85d6c..00000000 --- a/docs/content/scripts/content/google-maps.md +++ /dev/null @@ -1,384 +0,0 @@ ---- -title: Google Maps -description: Show performance-optimized Google Maps in your Nuxt app. -links: - - label: useScriptGoogleMaps - icon: i-simple-icons-github - to: https://github.com/nuxt/scripts/blob/main/src/runtime/registry/google-maps.ts - size: xs - - label: "" - icon: i-simple-icons-github - to: https://github.com/nuxt/scripts/blob/main/src/runtime/components/ScriptGoogleMaps.vue - size: xs ---- - -[Google Maps](https://maps.google.com/) allows you to embed maps in your website and customize them with your content. - -Nuxt Scripts provides a `useScriptGoogleMaps` composable and a headless `ScriptGoogleMaps` component to interact with the Google Maps. - -## ScriptGoogleMaps - -The `ScriptGoogleMaps` component is a wrapper around the `useScriptGoogleMaps` composable. It provides a simple way to embed Google Maps in your Nuxt app. - -It's optimized for performance by leveraging the [Element Event Triggers](/docs/guides/script-triggers#element-event-triggers), only loading the Google Maps when specific elements events happen. - -Before Google Maps is loaded, it shows a placeholder using [Maps Static API](https://developers.google.com/maps/documentation/maps-static). - -By default, it will load on the `mouseover` and `mouseclick` events. - -### Billing & Permissions - -::callout -You'll need an API key with permissions to access the [Static Maps API](https://developers.google.com/maps/documentation/maps-static/cloud-setup), the [Maps JavaScript API](https://developers.google.com/maps/documentation/javascript/cloud-setup) and [Places API](https://developers.google.com/maps/documentation/places/web-service/cloud-setup). -:: - -Showing an interactive JS map requires the Maps JavaScript API, which is a paid service. If a user interacts with the map, the following costs will be incurred: -- $7 per 1000 loads for the Maps JavaScript API (default for using Google Maps) -- $2 per 1000 loads for the Static Maps API - You can avoid providing a `placeholder` slot. -- $5 per 1000 loads for the Geocoding API - You can avoid this by providing a `google.maps.LatLng` object instead of a string for the `center` prop - -However, if the user never engages with the map, only the Static Maps API usage ($2 per 1000 loads) will be charged. - -Billing will be optimized in a [future update](https://github.com/nuxt/scripts/issues/83). - -You should consider using the [Iframe Embed](https://developers.google.com/maps/documentation/embed/get-started) instead if you want to avoid these costs -and are okay with a less interactive map. - -### Demo - -::code-group - -:google-maps-demo{label="Output"} - -```vue [Input] - - - -``` - -:: - -### Props - -The `ScriptGoogleMaps` component accepts the following props: - -**Map** - -- `center`: Where to center the map. You can provide a string with the location or use a `{ lat: 0, lng: 0 }` object. -- `apiKey`: The Google Maps API key. Must have access to the Static Maps API as well. You can optionally provide this as runtime config using the `public.scripts.googleMaps.apiKey` key. -- `centerMarker`: Whether to display a marker at the center position. Default is `true`. -- `mapOptions`: Options for the map. See [MapOptions](https://developers.google.com/maps/documentation/javascript/reference/map#MapOptions). - -**Placeholder** - -You can customize the placeholder image using the following props, alternatively, you can use the `#placeholder` slot to customize the placeholder image. - -- `placeholderOptions`: Customize the placeholder image attributes. See [Static Maps API](https://developers.google.com/maps/documentation/maps-static/start). -- `placeholderAttrs`: Customize the placeholder image attributes. - -**Sizing** - -If you want to render a map larger than 640x640 you should provide your own placeholder as the [Static Maps API](https://developers.google.com/maps/documentation/maps-static/start) -does not support rendering maps larger than this. - -- `width`: The width of the map. Default is `640`. -- `height`: The height of the map. Default is `400`. - -**Optimizations** - -- `trigger`: The trigger event to load the Google Maps. Default is `mouseover`. See [Element Event Triggers](/docs/guides/script-triggers#element-event-triggers) for more information. -- `aboveTheFold`: Optimizes the placeholder image for above-the-fold content. Default is `false`. - -**Markers** - -You can add markers to the static and interactive map by providing an array of `MarkerOptions`. See [MarkerOptions](https://developers.google.com/maps/documentation/javascript/reference/marker#MarkerOptions). - -- `markers`: An array of markers to display on the map. - -See the [markers](https://github.com/nuxt/scripts/blob/main/playground/pages/third-parties/google-maps/markers.vue) example for more information. - -### Guides - -#### Eager Loading Placeholder - -The Google Maps placeholder image is lazy-loaded by default. You should change this behavior if your map is above the fold -or consider using the `#placeholder` slot to customize the placeholder image. - -::code-group - -```vue [Placeholder Attrs] - -``` - -```vue [Placeholder Slot] - - - -``` - -:: - -#### Advanced Marker Control - -If you need more control over the markers on the map, you can use the exposed `createAdvancedMapMarker` function which -will return the marker instance. - -```vue - - -``` - - -#### Advanced Map Control - -The component exposes all internal APIs, so you can customize your map as needed. - -```vue - - -``` - - -### Component API - -See the [Facade Component API](/docs/guides/facade-components#facade-components-api) for full props, events, and slots. - -### Events - -The `ScriptGoogleMaps` component emits a single `ready` event when the Google Maps is loaded. - -```ts -const emits = defineEmits<{ - ready: [map: google.maps.Map] -}>() -``` - -To subscribe to Google Map events, you can use the `ready` event. - -```vue - - - -``` - -### Slots - -The component provides minimal UI by default, only enough to be functional and accessible. There are a number of slots for you to customize the maps however you like. - -**default** - -The default slot is used to display content that will always be visible. - -```vue - -``` - -**awaitingLoad** - -The slot is used to display content while the Google Maps is loading. - -```vue - -``` - -**loading** - -The slot is used to display content while the Google Maps is loading. - -Note: This shows a `ScriptLoadingIndicator` by default for accessibility and UX, by providing a slot you will -override this component. Make sure you provide a loading indicator. - -```vue - -``` - -**placeholder** - -The slot is used to display a placeholder image before the Google Maps is loaded. By default, this will show the Google Maps Static API image for the map. You can display it however you like. - -```vue - -``` - -## useScriptGoogleMaps - -The `useScriptGoogleMaps` composable lets you have fine-grain control over the Google Maps SDK. It provides a way to load the Google Maps SDK and interact with it programmatically. - -```ts -export function useScriptGoogleMaps(_options?: GoogleMapsInput) {} -``` - -Please follow the [Registry Scripts](/docs/guides/registry-scripts) guide to learn more about advanced usage. - -### GoogleMapsApi - -```ts -export interface GoogleMapsApi { - // @types/google.maps - maps: typeof google.maps -} -``` - -## Example - -Loading the Google Maps SDK and interacting with it programmatically. - -```vue - - -``` diff --git a/docs/content/scripts/crisp.md b/docs/content/scripts/crisp.md new file mode 100644 index 00000000..e6b86b7a --- /dev/null +++ b/docs/content/scripts/crisp.md @@ -0,0 +1,214 @@ +--- + +title: Crisp +description: Show performance-optimized Crisp in your Nuxt app. +links: + - label: useScriptCrisp + icon: i-simple-icons-github + to: https://github.com/nuxt/scripts/blob/main/src/runtime/registry/crisp.ts + size: xs + - label: "" + icon: i-simple-icons-github + to: https://github.com/nuxt/scripts/blob/main/src/runtime/components/ScriptCrisp.vue + size: xs + +--- + +[Crisp](https://crisp.chat/) is a customer messaging platform that lets you communicate with your customers through chat, email, and more. + +Nuxt Scripts provides a [`useScriptCrisp()`{lang="ts"}](#usescriptcrisp){lang="ts"} composable and a headless Facade Component [``{lang="html"}](#scriptcrisp){lang="html"} component to interact with crisp. + +::script-stats +:: + +::script-types +:: + +## [``{lang="html"}](/scripts/crisp){lang="html"} + +The [``{lang="html"}](/scripts/crisp){lang="html"} component is headless Facade Component wrapping the [`useScriptCrisp()`{lang="ts"}](#usescriptcrisp){lang="ts"} composable, providing a simple, performance optimized way to load Crisp in your Nuxt app. + +It's optimized for performance by using the [Element Event Triggers](/docs/guides/script-triggers#element-event-triggers), only loading crisp when specific elements events happen. + +By default, it will load on the `click` DOM event. + +### Demo + +::code-group + +:crisp-demo{label="Output"} + +```vue [Input] + + + + + +``` + +:: + +### Component API + +See the [Facade Component API](/docs/guides/facade-components#facade-components-api) for full props, events, and slots. + +#### With Environment Variables + +If you prefer to configure your id using environment variables. + +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + scripts: { + registry: { + crisp: true, + } + }, + // you need to provide a runtime config to access the environment variables + runtimeConfig: { + public: { + scripts: { + crisp: { + id: '', // NUXT_PUBLIC_SCRIPTS_CRISP_ID + }, + }, + }, + }, +}) +``` + +```text [.env] +NUXT_PUBLIC_SCRIPTS_CRISP_ID= +``` + +### Events + +The [``{lang="html"}](/scripts/crisp){lang="html"} component emits a single `ready` event when Crisp loads. + +```ts +const emits = defineEmits<{ + ready: [crisp: Crisp] +}>() +``` + +```vue + + + +``` + +### Slots + +**awaitingLoad** + +This slot displays content while Crisp is loading. + +```vue + +``` + +**loading** + +This slot displays content while Crisp is loading. + +Tip: You should use the `ScriptLoadingIndicator` by default for accessibility and UX. + +```vue + +``` + +## [`useScriptCrisp()`{lang="ts"}](/scripts/crisp){lang="ts"} + +The [`useScriptCrisp()`{lang="ts"}](/scripts/crisp){lang="ts"} composable lets you have fine-grain control over Crisp SDK. It provides a way to load crisp SDK and interact with it programmatically. + +```ts +export function useScriptCrisp(_options?: CrispInput) {} +``` + +Please follow the [Registry Scripts](/docs/guides/registry-scripts) guide to learn more about advanced usage. + +For more information, please refer to the [Crisp API documentation](https://docs.crisp.chat/guides/chatbox-sdks/web-sdk/dollar-crisp/). + +## Example + +Loading the Crisp SDK and interacting with it programmatically. + +```vue + +``` diff --git a/docs/content/scripts/databuddy-analytics.md b/docs/content/scripts/databuddy-analytics.md new file mode 100644 index 00000000..b80d30f9 --- /dev/null +++ b/docs/content/scripts/databuddy-analytics.md @@ -0,0 +1,34 @@ +--- + +title: Databuddy Analytics +description: Use Databuddy Analytics in your Nuxt app. +links: + - label: Source + icon: i-simple-icons-github + to: https://github.com/nuxt/scripts/blob/main/src/runtime/registry/databuddy-analytics.ts + size: xs + +--- + +[Databuddy](https://www.databuddy.cc/) is a privacy-first analytics platform focused on performance and minimal data collection. + +::script-stats +:: + +::script-docs +:: + +::script-types +:: + +### CDN / Self-hosted + +By default the registry injects `https://cdn.databuddy.cc/databuddy.js`. If you host the script yourself, pass `scriptUrl` in options to override the `src`. + +```ts +useScriptDatabuddyAnalytics({ + scriptInput: { src: 'https://my-host/databuddy.js' }, + clientId: 'YOUR_CLIENT_ID' +}) +``` + diff --git a/docs/content/scripts/fathom-analytics.md b/docs/content/scripts/fathom-analytics.md new file mode 100644 index 00000000..2cb75e56 --- /dev/null +++ b/docs/content/scripts/fathom-analytics.md @@ -0,0 +1,62 @@ +--- + +title: Fathom Analytics +description: Use Fathom Analytics in your Nuxt app. +links: + - label: Source + icon: i-simple-icons-github + to: https://github.com/nuxt/scripts/blob/main/src/runtime/registry/fathom-analytics.ts + size: xs + +--- + +[Fathom Analytics](https://usefathom.com/) is a great privacy analytics solution for your Nuxt app. It doesn't gather personal data from your visitors, yet provides detailed insights into how visitors use your site. + +::script-stats +:: + +::script-docs +:: + +## Defaults + +- **Trigger**: Script will load when Nuxt is hydrated. + +::script-types +:: + +You can access the `fathom` object as a proxy directly or await the `$script` promise to access the object. It's recommended +to use the proxy for any void functions. + +::code-group + +```ts [Proxy] +const { proxy } = useScriptFathomAnalytics() +function trackMyGoal() { + proxy.trackGoal('MY_GOAL_ID', 100) +} +``` + +```ts [onLoaded] +const { onLoaded } = useScriptFathomAnalytics() +onLoaded(({ trackGoal }) => { + trackGoal('MY_GOAL_ID', 100) +}) +``` + +:: + +## Example + +Loading Fathom Analytics through the `app.vue` when Nuxt is ready. + +```vue [app.vue] + +``` diff --git a/docs/content/scripts/google-adsense.md b/docs/content/scripts/google-adsense.md new file mode 100644 index 00000000..7380b3d7 --- /dev/null +++ b/docs/content/scripts/google-adsense.md @@ -0,0 +1,219 @@ +--- + +title: Google Adsense +description: Show Google Adsense ads in your Nuxt app. +links: + - label: useScriptGoogleAdsense + icon: i-simple-icons-github + to: https://github.com/nuxt/scripts/blob/main/src/runtime/registry/google-adsense.ts + size: xs + - label: "" + icon: i-simple-icons-github + to: https://github.com/nuxt/scripts/blob/main/src/runtime/components/ScriptGoogleAdsense.vue + size: xs + +--- + +[Google AdSense](https://www.google.com/adsense/start/) allows you to monetize your website by displaying relevant ads from Google. + +Nuxt Scripts provides: + +- [`useScriptGoogleAdsense()`{lang="ts"}](/scripts/google-adsense){lang="ts"}: A composable to manage Google AdSense dynamically. +- ``{lang="html"}: A headless component to embed ads directly in your Nuxt app. + +::script-stats +:: + +::script-types +:: + +## Global Setup + +You can configure Google AdSense **globally** in your `nuxt.config.ts` so that Nuxt automatically loads the script on all pages. + +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + scripts: { + registry: { + googleAdsense: { + client: 'ca-pub-', // Your Google AdSense Publisher ID + autoAds: true, // Enable Auto Ads + }, + }, + }, +}) +``` + +## Where to Find ``{lang="html"} (Publisher ID) + +Find your **Google AdSense Publisher ID** (also known as `ca-pub-XXXXXXX`) in your **Google AdSense Account**: + +1. Log in to your **Google AdSense** account. +2. Navigate to **Account > Settings** (click on your profile icon > "Account information"). +3. Locate the **Publisher ID** under **Account Information**. +4. Replace ``{lang="html"} in the config above with your actual ID. + +::callout{icon="i-heroicons-light-bulb" to="https://adsense.google.com/start/" target="_blank"} +You can also manage **Auto Ads settings** from your **Google AdSense Dashboard** to control *ad types, placements, and revenue optimization*. +:: + +## Site Ownership Verification + +### Automatic Meta Tag Insertion + +If you provide a `client`, Nuxt automatically inserts a **meta tag** on the page for Google to verify your site ownership. + +::tabs + ::div + --- + label: Example + icon: i-heroicons-code-bracket-square + --- + ```ts [nuxt.config.ts] + export default defineNuxtConfig({ + scripts: { + registry: { + googleAdsense: { + client: 'ca-pub-', // AdSense Publisher ID + }, + }, + }, + }) + ``` + :: + ::div + --- + label: Output + icon: i-heroicons-magnifying-glass-circle + --- + ```html + + ``` + :: +:: + +### Using `ads.txt` for Verification + +Google recommends adding an `ads.txt` file for **ad revenue eligibility**. + +#### Steps: + +1. Create a new file: `public/ads.txt` +2. Add the following content: + ```plaintext + google.com, pub-, DIRECT, f08c47fec0942fa0 + ``` +3. Replace ``{lang="html"} with your **AdSense Publisher ID**. + +::callout{icon="i-heroicons-light-bulb"} +**Why use `ads.txt`?** It helps **prevent ad fraud** and ensures that **only your site** can display your ads. +:: + +## Enabling Auto Ads + +Auto Ads allow Google to **automatically** place ads for **better optimization**. + +::tabs + ::div + --- + label: Example + icon: i-heroicons-code-bracket-square + --- + ```ts [nuxt.config.ts] + export default defineNuxtConfig({ + scripts: { + registry: { + googleAdsense: { + client: 'ca-pub-', // AdSense Publisher ID + autoAds: true, // Enable Auto Ads + }, + }, + }, + }) + ``` + :: + ::div + --- + label: Output + icon: i-heroicons-magnifying-glass-circle + --- + ```html + + ``` + :: +:: + +## Using [``{lang="html"}](/scripts/google-adsense){lang="html"} Component + +It provides a simple way to **embed ads** in your Nuxt app. + +```vue + +``` + +### Component Props + +| Prop | Description | +| ---------------------------- | --------------------------------------------------------------------- | +| `data-ad-client` | Your **Google Adsense Publisher ID**(`ca-pub-XXXXXXXXXX`). | +| `data-ad-slot` | Your **Ad Slot ID** (available in AdSense dashboard). | +| `data-ad-format` | Ad format type (`auto`, `rectangle`, `horizontal`, `vertical`, `fluid`, `autorelaxed`). | +| `data-ad-layout` | Layout (`in-article`, `in-feed`, `fixed`). | +| `data-full-width-responsive` | **Set to `true`** to make the ad responsive. | + +#### Example Usage with `data-ad-layout` + +To specify a layout for your ads (such as "in-article"), you can use the `data-ad-layout` attribute: + +```vue + +``` + +## How to Handle Ad-Blockers? + +If a user has an **ad-blocker enabled**, you can show **fallback content**. + +```vue + +``` + +## Using [`useScriptGoogleAdsense()`{lang="ts"}](/scripts/google-adsense){lang="ts"} Composable + +The [`useScriptGoogleAdsense()`{lang="ts"}](/scripts/google-adsense){lang="ts"} composable allows **fine-grain control** over the AdSense script. + +```ts +export function useScriptGoogleAdsense( + _options?: GoogleAdsenseInput +) {} +``` + +See the [Registry Scripts Guide](/docs/guides/registry-scripts) for advanced usage. + +::callout{icon="i-heroicons-light-bulb" to="https://support.google.com/adsense" target="_blank"} +Need more help? Check out the official **Google AdSense Guide** +:: diff --git a/docs/content/scripts/google-analytics.md b/docs/content/scripts/google-analytics.md new file mode 100644 index 00000000..d54cd46a --- /dev/null +++ b/docs/content/scripts/google-analytics.md @@ -0,0 +1,158 @@ +--- + +title: Google Analytics +description: Use Google Analytics in your Nuxt app. +links: + - label: Source + icon: i-simple-icons-github + to: https://github.com/nuxt/scripts/blob/main/src/runtime/registry/google-analytics.ts + size: xs + +--- + +[Google Analytics](https://marketingplatform.google.com/about/analytics/) is an analytics solution for Nuxt Apps. + +It provides detailed insights into how your website is performing, how users are interacting with your content, and how they are navigating through your site. + +::script-stats +:: + +::script-docs +:: + +### Usage + +To interact with the Google Analytics API, it's recommended to use script [proxy](/docs/guides/key-concepts#understanding-proxied-functions). + +```ts +const { proxy } = useScriptGoogleAnalytics() + +proxy.gtag('event', 'page_view') +``` + +The proxy exposes the `gtag` and `dataLayer` properties, and you should use them following Google Analytics best practices. + +::script-types +:: + +### Customer/Consumer ID Tracking + +For e-commerce or multi-tenant applications where you need to track customer-specific analytics alongside your main tracking: + +```vue [ProductPage.vue] + +``` + +## Custom Dimensions and User Properties + +```ts +const { proxy } = useScriptGoogleAnalytics() + +// User properties (persist across sessions) +proxy.gtag('set', 'user_properties', { + user_tier: 'premium', + account_type: 'business' +}) + +// Event with custom dimensions (register in GA4 Admin > Custom Definitions) +proxy.gtag('event', 'purchase', { + transaction_id: 'T12345', + value: 99.99, + payment_method: 'credit_card', // custom dimension + discount_code: 'SAVE10' // custom dimension +}) + +// Default params for all future events +proxy.gtag('set', { country: 'US', currency: 'USD' }) +``` + +## Manual Page View Tracking (SPAs) + +GA4 auto-tracks page views. To disable and track manually: + +```ts +const { proxy } = useScriptGoogleAnalytics() + +// Disable automatic page views +proxy.gtag('config', 'G-XXXXXXXX', { send_page_view: false }) + +// Track on route change +const router = useRouter() +router.afterEach((to) => { + proxy.gtag('event', 'page_view', { page_path: to.fullPath }) +}) +``` + +## Proxy Queuing + +The proxy queues all `gtag` calls until the script loads. Calls are SSR-safe, adblocker-resilient, and order-preserved. + +```ts +const { proxy, onLoaded } = useScriptGoogleAnalytics() + +// Fire-and-forget (queued until GA loads) +proxy.gtag('event', 'cta_click', { button_id: 'hero-signup' }) + +// Need return value? Wait for load +onLoaded(({ gtag }) => { + gtag('get', 'G-XXXXXXXX', 'client_id', id => console.log(id)) +}) +``` + +## Common Event Patterns + +```ts +const { proxy } = useScriptGoogleAnalytics() + +// E-commerce +proxy.gtag('event', 'purchase', { + transaction_id: 'T_12345', + value: 59.98, + currency: 'USD', + items: [{ item_id: 'SKU_12345', item_name: 'Widget', price: 29.99, quantity: 2 }] +}) + +// Engagement +proxy.gtag('event', 'login', { method: 'Google' }) +proxy.gtag('event', 'search', { search_term: 'nuxt scripts' }) + +// Custom +proxy.gtag('event', 'feature_used', { feature_name: 'dark_mode' }) +``` + +## Debugging + +Enable debug mode via config or URL param `?debug_mode=true`: + +```ts +proxy.gtag('config', 'G-XXXXXXXX', { debug_mode: true }) +``` + +View events in GA4: **Admin > DebugView**. Install [GA Debugger extension](https://chrome.google.com/webstore/detail/google-analytics-debugger/jnkmfdileelhofjcijamephohjechhna) for console logging. + +For consent mode setup, see the [Consent Guide](/docs/guides/consent). diff --git a/docs/content/scripts/google-maps.md b/docs/content/scripts/google-maps.md new file mode 100644 index 00000000..93cf292c --- /dev/null +++ b/docs/content/scripts/google-maps.md @@ -0,0 +1,715 @@ +--- + +title: Google Maps +description: Show performance-optimized Google Maps in your Nuxt app. +links: + - label: useScriptGoogleMaps + icon: i-simple-icons-github + to: https://github.com/nuxt/scripts/blob/main/src/runtime/registry/google-maps.ts + size: xs + - label: "" + icon: i-simple-icons-github + to: https://github.com/nuxt/scripts/blob/main/src/runtime/components/ScriptGoogleMaps.vue + size: xs + +--- + +[Google Maps](https://maps.google.com/) allows you to embed maps in your website and customize them with your content. + +Nuxt Scripts provides a [`useScriptGoogleMaps()`{lang="ts"}](/scripts/google-maps){lang="ts"} composable and a headless [``{lang="html"}](/scripts/google-maps){lang="html"} component to interact with the Google Maps. + +::script-stats +:: + +::script-types +:: + +## Types + +To use Google Maps with full TypeScript support, you will need +to install the `@types/google.maps` dependency. + +```bash +pnpm add -D @types/google.maps +``` + +## Setup + +To use the Google Maps component with server-side features (static map proxy, geocode proxy), enable it in your `nuxt.config`: + +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + scripts: { + registry: { + googleMaps: true, + }, + }, +}) +``` + +This registers server API routes for the static maps image proxy (`/_scripts/proxy/google-static-maps`) and geocode proxy (`/_scripts/proxy/google-maps-geocode`), keeping your API key server-side. + +## [``{lang="html"}](/scripts/google-maps){lang="html"} + +The [``{lang="html"}](/scripts/google-maps){lang="html"} component is a wrapper around the [`useScriptGoogleMaps()`{lang="ts"}](/scripts/google-maps){lang="ts"} composable. It provides a simple way to embed Google Maps in your Nuxt app. + +It's optimized for performance by using the [Element Event Triggers](/docs/guides/script-triggers#element-event-triggers), only loading the Google Maps when specific elements events happen. + +Before Google Maps loads, it shows a placeholder using [Maps Static API](https://developers.google.com/maps/documentation/maps-static). + +By default, it will load on the `mouseover` and `mouseclick` events. + +### Billing & Permissions + +::callout +You'll need an API key with permissions to access the [Maps JavaScript API](https://developers.google.com/maps/documentation/javascript/cloud-setup). + +Optionally, you can provide permissions to the [Static Maps API](https://developers.google.com/maps/documentation/maps-static/cloud-setup) (required when lazy loading and using the placeholder map) and [Places API](https://developers.google.com/maps/documentation/places/web-service/cloud-setup) (required when searching using a query, i.e "New York"). +:: + +Showing an interactive JS map requires the Maps JavaScript API, which is a paid service. If a user interacts with the map, the following costs will be incurred: +- $7 per 1000 loads for the Maps JavaScript API (default for using Google Maps) +- $2 per 1000 loads for the Static Maps API - Only used when you don't provide a `placeholder` slot. +- $5 per 1000 loads for the Geocoding API - Only used when you don't provide a `google.maps.LatLng` object instead of a query string for the `center` prop + +However, if the user never engages with the map, only the Static Maps API usage ($2 per 1000 loads) will be charged, assuming you're using it. + +Billing will be optimized in a [future update](https://github.com/nuxt/scripts/issues/83). + +You should consider using the [Iframe Embed](https://developers.google.com/maps/documentation/embed/get-started) instead if you want to avoid these costs +and are okay with a less interactive map. + +### Demo + +::code-group + +:google-maps-demo{label="Output"} + +```vue [Input] + + + +``` + +:: + +#### With Environment Variables + +If you prefer to configure your API key using environment variables. + +```ts [nuxt.config.ts] +export default defineNuxtConfig({ + scripts: { + registry: { + googleMaps: true, + } + }, + // you need to provide a runtime config to access the environment variables + runtimeConfig: { + public: { + scripts: { + googleMaps: { + apiKey: '', // NUXT_PUBLIC_SCRIPTS_GOOGLE_MAPS_API_KEY + }, + }, + }, + }, +}) +``` + +```text [.env] +NUXT_PUBLIC_SCRIPTS_GOOGLE_MAPS_API_KEY= +``` + +### Guides + +#### Eager Loading Placeholder + +The Google Maps placeholder image is lazy-loaded by default. You should change this behavior if your map is above the fold +or consider using the `#placeholder` slot to customize the placeholder image. + +::code-group + +```vue [Placeholder Attrs] + +``` + +```vue [Placeholder Slot] + + + +``` + +:: + +#### Advanced Marker Control + +If you need more control over the markers on the map, you can use the exposed `createAdvancedMapMarker` function which +will return the marker instance. + +```vue + + + +``` + + +#### Advanced Map Control + +The component exposes all internal APIs, so you can customize your map as needed. + +```vue + + + +``` + +#### Loading immediately + +If you want to load the Google Maps immediately, you can use the `trigger` prop. + +```vue + +``` + +#### Map Styling + +You can style the map by using the `mapOptions.styles` prop. You can find pre-made styles on [Snazzy Maps](https://snazzymaps.com/). + +This will automatically work for both the static map placeholder and the interactive map. + +```vue + + + +``` + +### Component API + +See the [Facade Component API](/docs/guides/facade-components#facade-components-api) for full props, events, and slots. + +### Events + +The [``{lang="html"}](/scripts/google-maps){lang="html"} component emits a single `ready` event when Google Maps loads. + +```ts +const emits = defineEmits<{ + ready: [map: google.maps.Map] +}>() +``` + +To subscribe to Google Map events, you can use the `ready` event. + +```vue + + + +``` + +### Slots + +The component provides minimal UI by default, only enough to be functional and accessible. There are a number of slots for you to customize the maps however you like. + +**default** + +The default slot displays content that will always be visible. + +```vue + +``` + +**awaitingLoad** + +This slot displays content while Google Maps is loading. + +```vue + +``` + +**loading** + +This slot displays content while Google Maps is loading. + +Note: This shows a `ScriptLoadingIndicator` by default for accessibility and UX, by providing a slot you will +override this component. Make sure you provide a loading indicator. + +```vue + +``` + +**placeholder** + +This slot displays a placeholder image before Google Maps loads. By default, this will show the Google Maps Static API image for the map. + +By providing your own placeholder slot, you disable the default placeholder image and won't incur charges for the Static Maps API. + +```vue + +``` + +## Google Maps SFC Components + +Nuxt Scripts provides individual Single File Components (SFCs) for different Google Maps elements. These components allow you to declaratively compose complex maps using Vue's template syntax. + +### Installation + +To use marker clustering functionality, you'll need to install the required peer dependency: + +```bash +npm install @googlemaps/markerclusterer +# or +yarn add @googlemaps/markerclusterer +# or +pnpm add @googlemaps/markerclusterer +``` + +### Available Components + +All Google Maps SFC components must work within a ``{lang="html"} component: + +- ``{lang="html"} - Classic markers with icon support +- ``{lang="html"} - Modern advanced markers with HTML content +- ``{lang="html"} - Customizable pin markers (use within AdvancedMarkerElement) +- ``{lang="html"} - Information windows that appear on click +- ``{lang="html"} - Groups nearby markers into clusters +- ``{lang="html"} - Circular overlays +- ``{lang="html"} - Polygon shapes +- ``{lang="html"} - Line paths +- ``{lang="html"} - Rectangular overlays +- ``{lang="html"} - Heatmap visualization + +### Basic Usage + +```vue + +``` + +### Component Composition Patterns + +**Marker Clustering** + +```vue + +``` + +**Heatmap with Data Points** + +```vue + + + +``` + +**See the [SFC Playground Example](https://nuxt-scripts-playground.stackblitz.io/third-parties/google-maps/sfcs) for a complete demonstration.** + +### Component Details + +#### ScriptGoogleMapsMarker + +Classic Google Maps marker with icon support. + +**Props:** +- `options` - `google.maps.MarkerOptions` (excluding `map`) + +**Events:** +- Standard marker events: `click`, `mousedown`, `mouseover`, etc. + +#### ScriptGoogleMapsAdvancedMarkerElement + +Modern advanced markers that support HTML content and better customization. + +**Props:** +- `options` - `google.maps.marker.AdvancedMarkerElementOptions` (excluding `map`) + +**Events:** +- Standard marker events: `click`, `drag`, `position_changed`, etc. + +#### ScriptGoogleMapsInfoWindow + +Information windows that display content when triggered. + +**Props:** +- `options` - `google.maps.InfoWindowOptions` + +**Behavior:** +- Automatically opens on parent marker click +- You can use it standalone with an explicit position +- Supports custom HTML content via default slot + +#### ScriptGoogleMapsMarkerClusterer + +Groups nearby markers into clusters for better performance and UX. + +**Props:** +- `options` - `MarkerClustererOptions` (excluding `map`) + +**Dependencies:** +- Requires `@googlemaps/markerclusterer` peer dependency + +#### Other Components + +- **ScriptGoogleMapsPinElement**: Use within AdvancedMarkerElement for customizable pins +- **ScriptGoogleMapsCircle**: Circular overlays with radius and styling +- **ScriptGoogleMapsPolygon/Polyline**: Shape and line overlays +- **ScriptGoogleMapsRectangle**: Rectangular overlays +- **ScriptGoogleMapsHeatmapLayer**: Data visualization with heatmaps + +All components support: +- Reactive `options` prop that updates the basic Google Maps object +- Automatic cleanup on component unmount +- TypeScript support with Google Maps types + +### Best Practices + +#### Performance Considerations + +**Use MarkerClusterer for Many Markers** +```vue + + + + + + + +``` + +**Prefer AdvancedMarkerElement for Modern Apps** +```vue + + + + + + + +``` + +#### Component Hierarchy + +Follow this nesting structure for components: + +``` +ScriptGoogleMaps (root) +├── ScriptGoogleMapsMarkerClusterer (optional) +│ └── ScriptGoogleMapsMarker/AdvancedMarkerElement +│ └── ScriptGoogleMapsInfoWindow (optional) +├── ScriptGoogleMapsAdvancedMarkerElement +│ ├── ScriptGoogleMapsPinElement (optional) +│ └── ScriptGoogleMapsInfoWindow (optional) +└── Other overlays (Circle, Polygon, etc.) +``` + +#### Reactive Data Patterns + +**Reactive Marker Updates** +```vue + + + +``` + +#### Error Handling + +Always provide error fallbacks and loading states: + +```vue + + + +``` + +## [`useScriptGoogleMaps()`{lang="ts"}](/scripts/google-maps){lang="ts"} + +The [`useScriptGoogleMaps()`{lang="ts"}](/scripts/google-maps){lang="ts"} composable lets you have fine-grain control over the Google Maps SDK. It provides a way to load the Google Maps SDK and interact with it programmatically. + +```ts +export function useScriptGoogleMaps(_options?: GoogleMapsInput) {} +``` + +Please follow the [Registry Scripts](/docs/guides/registry-scripts) guide to learn more about advanced usage. + +## Example + +Loading the Google Maps SDK and interacting with it programmatically. + +```vue + + + +``` diff --git a/docs/content/scripts/google-recaptcha.md b/docs/content/scripts/google-recaptcha.md new file mode 100644 index 00000000..69282d1b --- /dev/null +++ b/docs/content/scripts/google-recaptcha.md @@ -0,0 +1,254 @@ +--- + +title: Google reCAPTCHA +description: Use Google reCAPTCHA v3 in your Nuxt app. +links: + - label: Source + icon: i-simple-icons-github + to: https://github.com/nuxt/scripts/blob/main/src/runtime/registry/google-recaptcha.ts + size: xs + +--- + +[Google reCAPTCHA](https://www.google.com/recaptcha/about/) protects your site from spam and abuse using advanced risk analysis. + +Nuxt Scripts provides a registry script composable [`useScriptGoogleRecaptcha()`{lang="ts"}](/scripts/google-recaptcha){lang="ts"} to easily integrate reCAPTCHA v3 in your Nuxt app. + +::callout +This integration supports reCAPTCHA v3 (score-based, invisible) only. For v2 checkbox, use the standard reCAPTCHA integration. +:: + +::script-stats +:: + +::script-docs{:sections='["setup", "composable"]'} +:: + +::script-types +:: + +## Example + +Using reCAPTCHA v3 to protect a form submission with server-side verification. + +::code-group + +```vue [ContactForm.vue] + + +