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.
Per-script opt-out uses proxy: false in the registry entry:
export default defineNuxtConfig({
scripts: {
registry: {
googleAnalytics: { id: 'G-XXXXX', proxy: false },
}
}
})
Global privacy override (affects all proxied requests):
export default defineNuxtConfig({
scripts: {
privacy: { ip: true, userAgent: true }, // selective
// privacy: true, // full anonymization
// privacy: false, // passthrough
}
})
⚡ 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.
Set partytown: true per-script:
export default defineNuxtConfig({
modules: ['@nuxtjs/partytown', '@nuxt/scripts'],
scripts: {
registry: {
plausibleAnalytics: { domain: 'example.com', partytown: true },
fathomAnalytics: { site: 'XXXXX', partytown: true },
umamiAnalytics: { websiteId: 'xxx', partytown: true },
}
}
// Forward array auto-configured per-script!
})
Auto-forwarding supported for: googleAnalytics, plausibleAnalytics, fathomAnalytics, umamiAnalytics, matomoAnalytics, segment, mixpanelAnalytics, bingUet, 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
- Rybbit Analytics (PR #453): Privacy-focused open source analytics
- Databuddy Analytics (PR #495): Lightweight analytics
- 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 Overhaul
The Google Maps integration received a major DX overhaul for v1, making it feel like a native Vue component library rather than a wrapper around options bags.
Declarative SFC Components (PR #510): 11 composable components for markers, shapes, overlays, clustering, and more. All use Vue's injection system for parent/child communication and clean up automatically on unmount.
Custom Marker Content (PR #658): The #content slot on ScriptGoogleMapsMarker replaces the default pin with any HTML or Vue template. Build price tags, status badges, or any custom marker visual declaratively.
<ScriptGoogleMapsMarker :position="{ lat: -34.397, lng: 150.644 }">
<template #content>
<div class="price-tag">$420k</div>
</template>
</ScriptGoogleMapsMarker>
Custom Overlay View (PR #658): ScriptGoogleMapsOverlayView renders arbitrary Vue content at a map position with full styling control. When nested inside a marker, it auto-inherits position and follows the marker during drag. Supports v-model:open for toggling visibility without remounting.
<ScriptGoogleMapsMarker :position="pos" @click="open = !open">
<ScriptGoogleMapsOverlayView v-model:open="open" anchor="bottom-center" :offset="{ x: 0, y: -50 }">
<MyCustomPopup @close="open = false" />
</ScriptGoogleMapsOverlayView>
</ScriptGoogleMapsMarker>
Direct :position and :zoom Props: Marker components now accept :position as a top-level prop (no options bag needed for the most common case). The root ScriptGoogleMaps component accepts a reactive :zoom prop.
Infrastructure:
- Color mode support (PR #587): Auto light/dark map switching with
mapIds prop
- Static maps proxy: Placeholder images routed through your server, API keys stay server-side
- Geocode proxy: Server-side geocoding reduces billing and hides API keys
- Memory leak fixes (PR #651):
useGoogleMapsResource composable ensures all sub-components clean up safely on unmount, even across async boundaries
- Marker clustering perf (PR #517, PR #653): Batch operations with
noDraw flag to avoid multiple rerenders
Deprecation: The legacy ScriptGoogleMapsMarker (wrapping google.maps.Marker) and ScriptGoogleMapsAdvancedMarkerElement names have been consolidated into ScriptGoogleMapsMarker (wrapping google.maps.marker.AdvancedMarkerElement). We removed ScriptGoogleMapsPinElement; use the #content slot on ScriptGoogleMapsMarker instead.
🏷️ 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
- Google Maps:
ScriptGoogleMapsAdvancedMarkerElement and ScriptGoogleMapsPinElement removed, consolidated into ScriptGoogleMapsMarker
Low Impact:
- GTM:
onBeforeGtmStart callback timing changed
- Type templates reorganized (run
nuxi prepare)
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.
Per-script opt-out uses
proxy: falsein the registry entry:Global privacy override (affects all proxied requests):
⚡ 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.
Set
partytown: trueper-script:Auto-forwarding supported for:
googleAnalytics,plausibleAnalytics,fathomAnalytics,umamiAnalytics,matomoAnalytics,segment,mixpanelAnalytics,bingUet,metaPixel,xPixel,tiktokPixel,snapchatPixel,redditPixel,cloudflareWebAnalytics🐦 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.
📦 New Registry Scripts
Other Changes
✋ Consent Trigger Revocation
The
useScriptTriggerConsent()composable now supports revoking consent at runtime, not just granting it.See PR #631.
🔄 Script Reload API
Scripts now expose a
.reload()method for re-executing DOM-scanning scripts after SPA navigation.See commit 77f853b.
🔐 Automatic SRI Integrity Hashes
Bundled scripts can automatically generate Subresource Integrity hashes.
See PR #575.
📊 Script Stats Export
New
@nuxt/scripts/statssubpath export for auditing script privacy, performance, and security characteristics.🎬 YouTube Player Overhaul
ratioprop📹 Vimeo Player Enhancements
ratioprop, matching YouTube Player API🗺️ Google Maps Overhaul
The Google Maps integration received a major DX overhaul for v1, making it feel like a native Vue component library rather than a wrapper around options bags.
Declarative SFC Components (PR #510): 11 composable components for markers, shapes, overlays, clustering, and more. All use Vue's injection system for parent/child communication and clean up automatically on unmount.
Custom Marker Content (PR #658): The
#contentslot onScriptGoogleMapsMarkerreplaces the default pin with any HTML or Vue template. Build price tags, status badges, or any custom marker visual declaratively.Custom Overlay View (PR #658):
ScriptGoogleMapsOverlayViewrenders arbitrary Vue content at a map position with full styling control. When nested inside a marker, it auto-inherits position and follows the marker during drag. Supportsv-model:openfor toggling visibility without remounting.Direct
:positionand:zoomProps: Marker components now accept:positionas a top-level prop (no options bag needed for the most common case). The rootScriptGoogleMapscomponent accepts a reactive:zoomprop.Infrastructure:
mapIdspropuseGoogleMapsResourcecomposable ensures all sub-components clean up safely on unmount, even across async boundariesnoDrawflag to avoid multiple rerendersDeprecation: The legacy
ScriptGoogleMapsMarker(wrappinggoogle.maps.Marker) andScriptGoogleMapsAdvancedMarkerElementnames have been consolidated intoScriptGoogleMapsMarker(wrappinggoogle.maps.marker.AdvancedMarkerElement). We removedScriptGoogleMapsPinElement; use the#contentslot onScriptGoogleMapsMarkerinstead.🏷️ GTM Consent Mode
See PR #544.
PayPal SDK v6
Migrated to PayPal JavaScript SDK v6. The component API has changed significantly.
See PR #628.
/web-sdk/v6/core)clientId(with optionalclientToken) orclientTokenalonetruein developmentYouTube Player
Aspect Ratio: Now controlled via
ratioprop instead ofwidth/height.Placeholder Image: Default
object-fitchanged fromcontaintocover. Useplaceholder-object-fit="contain"for old behavior.Google Tag Manager
onBeforeGtmStart Callback: Now fires for cached/pre-initialized scripts. Guard if needed:
🐛 Bug Fixes
cdfb697fix(rybbit): queue custom events before script loads (#585)f8ce5a1fix(gtm): invoke onBeforeGtmStart callback when ID is in config (#584)a8d20b0fix: addestree-walkeras a dependency (#583)4c79486fix(plausible): use consistent window reference in clientInit stub (#574)78367b1fix(matomo): respect user-provided URL protocol (#572)c685f43fix: broken type augmentinge2050a2fix: align templates with existing augments (#589)da3a8ccchore: include.nuxttypes (#588)039380efix: prevent memory leaks in all Google Maps sub-components (#651)7e139b3fix: avoid mutating runtimeConfig scriptOptions (#638)01b1af4fix: expand self-closing<Script*>tags to prevent SFC extraction issues (#613)c3a6098fix: 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:
Medium Impact:
ratioprop replaces width/height aspect calculationobject-fitdefault changed tocoverScriptGoogleMapsAdvancedMarkerElementandScriptGoogleMapsPinElementremoved, consolidated intoScriptGoogleMapsMarkerLow Impact:
onBeforeGtmStartcallback timing changednuxi prepare)