Skip to content

v1 Release #594

@harlan-zw

Description

@harlan-zw

Nuxt Scripts v1 is the first stable release.

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: scripts are bundled at build time and served 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.

🛠️ Rebuilt DevTools

The Nuxt DevTools panel has been rewritten around the v1 privacy and capability model. New views surface the things v1 does differently.

Image

🎉 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

⚠️ 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, 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.

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.

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()

📦 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 ratio prop
  • Proper cleanup: Players destroyed on unmount

📹 Vimeo Player Enhancements

  • Aspect ratio control (#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 (#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 #placeholder on ScriptGoogleMaps, or standalone for store locators and contact pages that never need the interactive Maps API.
  • ScriptGoogleMapsGeoJson (#656): Declarative wrapper around google.maps.Data for loading and styling GeoJSON with full event bindings.

Infrastructure:

  • Color mode support (#587): Auto light/dark map switching with mapIds prop
  • Geocode proxy: Server-side geocoding reduces billing and hides API keys
  • Memory leak fixes (#651): useGoogleMapsResource composable ensures all sub-components clean up safely on unmount, even across async boundaries
  • Marker clustering perf (#517, #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.

🔧 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

  • 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)
  • 96db067 fix: missing Bing UET types (#710)
  • fdaf089 fix(types): prevent registry entry types from collapsing to never (#701)
  • 698a585 fix: inherit registry scriptOptions in composable instances (#691)
  • 506dfd8 fix(google-maps): guard pan-on-open for closed/unpositioned overlay (#698)
  • 6fdd533 fix(google-maps): prevent center reset on re-render and export clusterer types (#686)
  • 0e4ae9e fix(google-maps): prevent zoom/pan reset when overlay toggles (#685)
  • 33ebf61 fix(google-maps): close races in resolveQueryToLatLng (#693)
  • 4df13ed fix: add defineSlots to all components for proper slot type inference (#684)
  • afbb6bd fix(types): broken IDE display of registry types (#683)
  • 63d4da5 fix: add v0 migration warnings and docs for breaking config changes (#679)
  • 3007458 fix(google-analytics): add www.google.com and www.googletagmanager.com to proxy domains (#678)
  • 1c47e72 fix: add missing proxy domains across registry scripts
  • e6ecfff fix(google-maps): bind $attrs to overlay view (#672)
  • c2f8a8b fix(google-maps): remove redundant slot props from OverlayView (#666)
  • b65e5db fix(google-maps): OverlayView, InfoWindow, and AdvancedMarkerElement DX issues (#660)
  • a597de9 fix: prevent memory leak in ScriptGoogleMapsAdvancedMarkerElement (#649)
  • 32ff4fa fix: improve registry script DX and type safety (#647)
  • 569eeeb fix(first-party): inject proxy endpoints for boolean/mock registry entries (#640)
  • 00fbaae fix: allow custom script keys in ScriptRegistry type (#632)
  • 3b93696 fix(plausible): broken firstParty mode
  • 1c31a71 fix(gravatar): broken proxy

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