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: Privacy Focused Proxy
Every third-party script request exposes your users data to fingerprinting.
Different providers are more intrusive, for example, the X Pixel accesses 9 browser fingerprinting APIs (including navigator.getBattery()), sets 5 tracking cookies, and makes requests to 3 separate domains. Microsoft Clarity reads 10 fingerprinting APIs across 3 domains.
First-party mode acts as a reverse proxy: Nuxt bundles scripts at build time and serves them from your domain, while runtime requests are securely forwarded through your server. Data sent to third-party servers gets anonymised: IPs (180.233.124.74 -> 180.233.124.0), browser versions (Mozilla/5.0 (compatible; Chrome/120.0)) and more. This is auto-enabled for all scripts that support it.
A side-effect is a performance boost from avoiding extra DNS lookups, fewer cookie banners, and reducing expensive fingerprinting queries. It also makes ad-blockers ineffective since requests appear same-origin.
See the First-Party Mode Guide and PR #577 for details.
⚡ 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
🐦 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 securely 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>
✋ First-Class Consent Controls
v1 treats consent as a first-class concern with two complementary APIs. See the Consent Guide, PR #544, PR #631, and PR #712.
Consent Trigger gates whether a script loads at all. The useScriptTriggerConsent() composable now supports revoking consent at runtime, not just granting it:
const trigger = useScriptTriggerConsent()
useScriptGoogleTagManager({ id: 'GTM-XXX', scriptOptions: { trigger } })
trigger.accept() // Grant → script loads
trigger.revoke() // Revoke → script blocked
trigger.consented // Ref<boolean>
Vendor-Native Controls: for scripts that are already loaded, every consent-aware registry script now exposes a vendor-native consent object on the returned instance plus a typed defaultConsent option applied before the first tracking call. Supported across Google Analytics, Google Tag Manager, Bing UET, Meta Pixel, TikTok Pixel, Matomo, Mixpanel, PostHog, and Clarity. Each vendor keeps its own dialect (GCMv2 for Google/Bing, grant/revoke for Meta, opt_in/opt_out for Mixpanel/PostHog, etc.), so docs and types line up one-to-one. #nuxt-scripts/types exports the GCMv2 ConsentState.
const gtm = useScriptGoogleTagManager({
id: 'GTM-XXX',
defaultConsent: { ad_storage: 'denied', analytics_storage: 'denied' },
})
// Later, after the user accepts
gtm.consent.update({ ad_storage: 'granted', analytics_storage: 'granted' })
const ttq = useScriptTikTokPixel({ id: '...', defaultConsent: 'hold' })
ttq.consent.grant() // or .revoke() / .hold()
const meta = useScriptMetaPixel({ id: '...', defaultConsent: 'denied' })
meta.consent.grant()
🛠️ Rebuilt DevTools
The Nuxt DevTools panel has been rewritten around the v1 privacy and capability model. New views surface the things v1 does differently:
- First-party panel: inspects every proxy route, shows the anonymization flags active per endpoint (IP, user agent, language, screen, timezone, hardware), and highlights which third-party domains are being routed through your server.
- Registry panel: capability matrix showing which scripts support
bundle,proxy, andpartytown, plus which capabilities are active by default vs. opt-in for the script. - Script inspection: per-script status, load timing, network waterfall, bundle size, and inline documentation.
- Standalone connect view: connect DevTools to a production build for on-the-fly inspection without running the full DevTools runtime in production.
📦 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
- Rybbit Analytics (#453): Privacy-focused open source analytics
- Databuddy Analytics (#495): Lightweight analytics
- 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
🔄 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 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 (#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 (#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 (#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 Prop: Marker components now accept :position as a top-level prop (no options bag needed for the most common case).
Additional Components:
ScriptGoogleMapsStaticMap(#673): The static placeholder is now a standalone component, with images routed through your server so API keys stay server-side. Use it inside#placeholderonScriptGoogleMaps, or standalone for store locators and contact pages that never need the interactive Maps API.ScriptGoogleMapsGeoJson(#656): Declarative wrapper aroundgoogle.maps.Datafor loading and styling GeoJSON with full event bindings.
Infrastructure:
- Color mode support (#587): Auto light/dark map switching with
mapIdsprop - Geocode proxy: Server-side geocoding reduces billing and hides API keys
- Memory leak fixes (#651):
useGoogleMapsResourcecomposable ensures all sub-components clean up safely on unmount, even across async boundaries - Marker clustering perf (#517, #653): Batch operations with
noDrawflag 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.
🔧 Environment-Variable Config
The module now auto-populates runtimeConfig.public.scripts defaults for any enabled registry entry. Script IDs, keys, and domains resolve from NUXT_PUBLIC_SCRIPTS_* env vars without any runtimeConfig boilerplate. See PR #634.
# .env
NUXT_PUBLIC_SCRIPTS_GOOGLE_ANALYTICS_ID=G-XXXXXX
NUXT_PUBLIC_SCRIPTS_POSTHOG_API_KEY=phc_xxx
⚠️ Breaking Changes & Migration
Upgrading from v0? See the v0 to v1 migration guide for the summary table and before/after diffs for every breaking change and deprecation.
🐛 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)96db067fix: missing Bing UET types (#710)fdaf089fix(types): prevent registry entry types from collapsing tonever(#701)698a585fix: inherit registry scriptOptions in composable instances (#691)506dfd8fix(google-maps): guard pan-on-open for closed/unpositioned overlay (#698)6fdd533fix(google-maps): prevent center reset on re-render and export clusterer types (#686)0e4ae9efix(google-maps): prevent zoom/pan reset when overlay toggles (#685)33ebf61fix(google-maps): close races inresolveQueryToLatLng(#693)4df13edfix: adddefineSlotsto all components for proper slot type inference (#684)afbb6bdfix(types): broken IDE display of registry types (#683)63d4da5fix: add v0 migration warnings and docs for breaking config changes (#679)3007458fix(google-analytics): addwww.google.comandwww.googletagmanager.comto proxy domains (#678)1c47e72fix: add missing proxy domains across registry scriptse6ecffffix(google-maps): bind$attrsto overlay view (#672)c2f8a8bfix(google-maps): remove redundant slot props from OverlayView (#666)b65e5dbfix(google-maps): OverlayView, InfoWindow, and AdvancedMarkerElement DX issues (#660)a597de9fix: prevent memory leak inScriptGoogleMapsAdvancedMarkerElement(#649)32ff4fafix: improve registry script DX and type safety (#647)569eeebfix(first-party): inject proxy endpoints for boolean/mock registry entries (#640)00fbaaefix: allow custom script keys inScriptRegistrytype (#632)3b93696fix(plausible): broken firstParty mode1c31a71fix(gravatar): broken proxy