v1.0.0
Nuxt Scripts v1 is the first stable release, pushing the ecosystem forward for better privacy and performance for third-party scripts.
📣 Highlights
🔒 First-Party Mode: Reverse Proxy Everything
When a user visits your site and you load third-party scripts, you 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 your 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 the First-Party Guide and PR #577 for 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
🐦 SSR Social Embeds
Third-party embed scripts (Twitter widgets, Instagram embeds, Bluesky posts) hurt performance and leak user data. Following the Cloudflare Zaraz approach, Nuxt Scripts now fetches embed data server-side and proxies 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 (#568): Product analytics with feature flags
- Google reCAPTCHA v3 (#567): Invisible bot protection
- TikTok Pixel (#569): Conversion tracking
- Google Sign-In (#573): One-tap authentication
- Bing UET (#650): Microsoft Advertising conversion tracking
- Mixpanel Analytics (#648): Product analytics and user tracking
- Vercel Analytics (#605): Vercel Web Analytics integration
- Gravatar (#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 (#586): Multiple players work correctly
- Aspect ratio control: New
ratioprop - Proper cleanup: Players destroyed on unmount
📹 Vimeo Player Enhancements
- Aspect ratio control (#624): New
ratioprop, matching YouTube Player API
🗺️ Google Maps Enhancements
- Color mode support (#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 optionalclientToken) orclientTokenalone - Sandbox mode defaults to
truein 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
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: v0 to v1 Migration Guide
Breaking Changes by Impact
High Impact:
- PayPal: Migrated to SDK v6 - component API changed
Medium Impact:
- YouTube Player:
ratioprop replaces width/height aspect calculation - YouTube Player: Placeholder
object-fitdefault changed tocover
Low Impact:
- GTM:
onBeforeGtmStartcallback timing changed - Type templates reorganized (run
nuxi prepare)