Skip to content

v1 Release #594

@harlan-zw

Description

@harlan-zw

Nuxt Scripts v1 is the first stable release.

Nuxt Scripts v1 is here, and we're pushing the ecosystem forward for better privacy and performance for third-party scripts.

📣 Highlights

🔒 First-Party Mode: Reverse Proxy Everything

When a user visits our site, and we are loading third-party scripts, we are breaking their trust at some level by passing on their data to third-party providers.

Every request to a new server passes along the user's IP and data that can be used to fingerprint them. Different providers are more intrusive, for example, the X Pixel accesses 9 browser fingerprinting APIs (including navigator.getBattery()), sets 5 tracking cookies (muc_ads, guest_id_marketing, guest_id_ads, personalization_id, guest_id), and phones home to 3 separate domains. Even Microsoft Clarity reads 10 fingerprinting APIs across 3 domains.

First-party mode flips the switch on this. You now own our users' requests by proxying everything. They go through your servers, which drop fingerprinting data. You honour their anonymity. This is now on by default.

What this means in practice is data sent to third-party servers get anonymized such as IPs (180.233.124.74 -> 180.233.124.0) and browser versions (Mozilla/5.0 (compatible; Chrome/120.0))

A side-effect of this is a performance boost by not hitting different domains, fewer cookie banners, and reducing expensive fingerprinting queries. It also makes ad-blockers ineffective.

See PR #577 for full details.

export default defineNuxtConfig({
  scripts: {
    firstParty: false, // opt-out
  }
})

⚡ Partytown Web Worker Support

Load third-party scripts off the main thread using Partytown. Scripts run in a web worker, freeing the main thread for your app. Integrates directly with first-party mode.

See PR #576.

export default defineNuxtConfig({
  modules: ['@nuxtjs/partytown', '@nuxt/scripts'],
  scripts: {
    partytown: ['plausible', 'fathom', 'umami'],
    registry: {
      plausible: { domain: 'example.com' },
      fathom: { site: 'XXXXX' }
    }
  }
  // Forward array auto-configured — no manual setup needed!
})

Auto-forwarding supported for: googleAnalytics, plausible, fathom, umami, matomo, segment, metaPixel, xPixel, tiktokPixel, snapchatPixel, redditPixel, cloudflareWebAnalytics

⚠️ Note: GA4 has known issues with Partytown. GTM is not compatible (requires DOM access). Consider Plausible, Fathom, or Umami instead.

🐦 SSR Social Embeds

Third-party embed scripts (Twitter widgets, Instagram embeds, Bluesky posts) hurt performance and leak user data. Following the Cloudflare Zaraz approach, we now fetch embed data server-side and proxy all assets through your domain.

See PR #590.

<ScriptXEmbed tweet-id="1754336034228171055">
  <template #default="{ userName, text, likesFormatted, photos }">
    <!-- Full styling control via scoped slots -->
  </template>
</ScriptXEmbed>

<ScriptInstagramEmbed post-url="https://instagram.com/p/ABC123/">
  <template #default="{ html, shortcode }">
    <div v-html="html" />
  </template>
</ScriptInstagramEmbed>

<ScriptBlueskyEmbed post-url="https://bsky.app/profile/...">
  <template #default="{ html }">
    <div v-html="html" />
  </template>
</ScriptBlueskyEmbed>

📦 New Registry Scripts

  • PostHog Analytics (PR #568) — Product analytics with feature flags
  • Google reCAPTCHA v3 (PR #567) — Invisible bot protection
  • TikTok Pixel (PR #569) — Conversion tracking
  • Google Sign-In (PR #573) — One-tap authentication
  • Bing UET (PR #650) — Microsoft Advertising conversion tracking
  • Mixpanel Analytics (PR #648) — Product analytics and user tracking
  • Vercel Analytics (PR #605) — Vercel Web Analytics integration
  • Gravatar (PR #606) — Avatar service with privacy-preserving proxy

Other Changes

✋ Consent Trigger Revocation

The useScriptTriggerConsent() composable now supports revoking consent at runtime, not just granting it.

See PR #631.

const trigger = useScriptTriggerConsent()
useScriptGoogleTagManager({ id: 'GTM-XXX', scriptOptions: { trigger } })

// Grant consent
trigger.accept()

// Revoke consent
trigger.revoke()

// Reactive consent state
trigger.consented // Ref<boolean>

🔄 Script Reload API

Scripts now expose a .reload() method for re-executing DOM-scanning scripts after SPA navigation.

See commit 77f853b.

const script = useScript('/third-party.js')
await script.reload()

🔐 Automatic SRI Integrity Hashes

Bundled scripts can automatically generate Subresource Integrity hashes.

See PR #575.

export default defineNuxtConfig({
  scripts: {
    assets: {
      integrity: 'sha384'
    }
  }
})

📊 Script Stats Export

New @nuxt/scripts/stats subpath export for auditing script privacy, performance, and security characteristics.

import { getScriptStats } from '@nuxt/scripts/stats'

const stats = await getScriptStats()
// Privacy ratings (A+ to F), performance ratings, CWV estimates,
// cookie analysis, network behavior, tracked data types

🎬 YouTube Player Overhaul

  • Isolated player instances (PR #586) — Multiple players work correctly
  • Aspect ratio control — New ratio prop
  • Proper cleanup — Players destroyed on unmount

📹 Vimeo Player Enhancements

  • Aspect ratio control (PR #624) — New ratio prop, matching YouTube Player API

🗺️ Google Maps Enhancements

  • Color mode support (PR #587) — Auto light/dark switching
  • Static maps proxy — Hide API keys, fix CORS
  • Geocode proxy — Server-side geocoding to reduce billing costs and hide API keys

🏷️ GTM Consent Mode

export default defineNuxtConfig({
  scripts: {
    registry: {
      googleTagManager: {
        id: 'GTM-XXXX',
        defaultConsent: {
          ad_storage: 'denied',
          analytics_storage: 'denied'
        }
      }
    }
  }
})

See PR #544.

⚠️ Breaking Changes

PayPal SDK v6

Migrated to PayPal JavaScript SDK v6. The component API has changed significantly.

See PR #628.

  • SDK URLs now use v6 endpoints (/web-sdk/v6/core)
  • New authentication modes: clientId (with optional clientToken) or clientToken alone
  • Sandbox mode defaults to true in development

YouTube Player

Aspect Ratio: Now controlled via ratio prop instead of width/height.

<ScriptYouTubePlayer
  video-id="..."
-  :width="1280"
-  :height="720"
+  ratio="16/9"
/>

Placeholder Image: Default object-fit changed from contain to cover. Use placeholder-object-fit="contain" for old behavior.

Google Tag Manager

onBeforeGtmStart Callback: Now fires for cached/pre-initialized scripts. Guard if needed:

let initialized = false
useScriptGoogleTagManager({
  onBeforeGtmStart: (gtag) => {
    if (initialized) return
    initialized = true
  }
})

🐛 Bug Fixes

  • cdfb697 fix(rybbit): queue custom events before script loads (#585)
  • f8ce5a1 fix(gtm): invoke onBeforeGtmStart callback when ID is in config (#584)
  • a8d20b0 fix: add estree-walker as a dependency (#583)
  • 4c79486 fix(plausible): use consistent window reference in clientInit stub (#574)
  • 78367b1 fix(matomo): respect user-provided URL protocol (#572)
  • c685f43 fix: broken type augmenting
  • e2050a2 fix: align templates with existing augments (#589)
  • da3a8cc chore: include .nuxt types (#588)
  • 039380e fix: prevent memory leaks in all Google Maps sub-components (#651)
  • 7e139b3 fix: avoid mutating runtimeConfig scriptOptions (#638)
  • 01b1af4 fix: expand self-closing <Script*> tags to prevent SFC extraction issues (#613)
  • c3a6098 fix: preserve compressed/binary request bodies in proxy handler (#619)

📖 Migration Guide

Full migration guide: https://scripts.nuxt.com/docs/migration-guide/v0-to-v1

Breaking Changes by Impact

High Impact:

  • PayPal: Migrated to SDK v6 — component API changed

Medium Impact:

  • YouTube Player: ratio prop replaces width/height aspect calculation
  • YouTube Player: Placeholder object-fit default changed to cover

Low Impact:

  • GTM: onBeforeGtmStart callback timing changed
  • Type templates reorganized (run nuxi prepare)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions