# Nuxt Scripts > Nuxt Scripts lets you load third-party scripts with better performance, privacy, security and DX. It includes many popular third-parties out of the box. Canonical Origin: https://scripts.nuxt.com/ ## LLM Resources - [Full Content](https://scripts.nuxt.com/llms-full.txt): Complete page content in markdown format. --- - **Page:** Introduction - **Source:** https://scripts.nuxt.com/docs/v0/getting-started - **Description:** Nuxt Scripts is Nuxt DX for third-party scripts. Nuxt Scripts enhances the performance, privacy, and developer experience (DX) when incorporating third-party scripts into Nuxt applications. ## Background Using the `useHead` composable to load third-party IIFE scripts is straightforward, but complexities arise with server-side rendering (SSR), lazy loading, and type safety. Nuxt Scripts addresses these challenges by optimizing third-party script integration to improve performance, privacy, and overall DX. ### Third-Party Script Challenges - Compatibility across client and server-side environments. - Application of best-practice defaults. - Granular optimization controls. - Avoiding render-blocking, privacy issues, and performance bottlenecks. - Ensuring type safety and script validation. - Security considerations with third-party servers. ### How Third Parties Can Impact User Experience Third-party resources like analytics tools, video embeds, maps, and social media integrations enhance website functionality but aren't directly managed by site owners. A single resource may have a minimal performance impact, but multiple resources can significantly degrade user experience. Scripts, in particular, can delay interactivity and obstruct page rendering. According to the Chrome User Experience Report, Nuxt sites with numerous third-party resources typically show lower [Interaction to Next Paint (INP)](https://web.dev/articles/inp) and [Largest Contentful Paint (LCP)](https://web.dev/articles/lcp) scores. Despite the correlation not proving causation, lab tests and the [Web Almanac](https://almanac.httparchive.org/en/2022/third-parties) confirm significant performance impacts from third-party resources. ## Nuxt Script Features ### ๐ŸŽ๏ธ Performance - Script loading is triggered only when Nuxt is ready, by default. - More advanced triggering of script loads, independent of implementation specifics. - Improved script loading times with [Bundling Remote Scripts](/docs/guides/bundling). ### ๐Ÿ˜Œ Developer Experience - A curated script registry for common third-party applications. - Simplified analytics event management, e.g., tracking page views. - Input validation for script options. - Type safety and SSR-compliant APIs. ### ๐Ÿ”’ Privacy - Minimize user data exposure by [Bundling Remote Scripts](/docs/guides/bundling). - Integrated consent management. - Enhanced script privacy settings such as `crossorigin="anonymous"` and `referrerpolicy="no-referrer"`. --- - **Page:** Introduction - **Source:** https://scripts.nuxt.com/docs/getting-started - **Description:** Load third-party scripts in Nuxt with typed APIs, loading controls, and privacy defaults. Nuxt Scripts gives Nuxt apps a consistent way to load third-party scripts, with controls for timing, privacy, and type safety. ## Background `useHead` works well for adding a script tag. The setup becomes harder once the script must account for server-side rendering (SSR), delayed loading, or a typed global API. Nuxt Scripts handles those cases through one API. ### Third-Party Script Challenges - Running safely in both client and server code. - Choosing when the browser loads a script. - Avoiding render-blocking work and unnecessary data exposure. - Validating configuration and typing the script API. ### How Third Parties Can Impact User Experience Analytics, video embeds, maps, and social widgets all run code that you do not control. One integration may be cheap; several can compete with hydration, delay interactivity, and block rendering. [Third-party scripts can add network overhead and delay document parsing](https://web.dev/articles/third-party-javascript), depending on how they load. The [2025 Web Almanac chapter on Third Parties](https://almanac.httparchive.org/en/2025/third-parties) found that request counts rose even as pages contacted fewer unique third-party domains. Counting vendors alone can therefore understate how much work they add to a page. ## Nuxt Scripts Features ### Loading - By default, scripts load with the `onNuxtReady` trigger, after hydration and during an idle period. - Trigger scripts from consent, visibility, interaction, or application state. - Serve supported scripts from your origin with [First-Party Mode](/docs/guides/first-party). ### Developer APIs - Registry entries for common third-party applications. - Page-event helpers for analytics integrations. - Input validation for script options. - Type safety and SSR-compliant APIs. ### Privacy - Minimize user data exposure with the [privacy-focused proxy](/docs/guides/first-party). - Integrated consent management. - Privacy defaults such as `crossorigin="anonymous"` and `referrerpolicy="no-referrer"`. --- - **Page:** Load Third-Party Scripts in Nuxt ยท Nuxt Scripts - **Source:** https://scripts.nuxt.com/ - **Description:** Better performance, privacy, security and DX for third-party scripts. Nuxt Scripts home Nuxt Scripts on GitHub [**v1 stable is here **](https://scripts.nuxt.com/docs/releases/v1) # **Load Third-Party Scripts**
** **with Nuxt Scripts Load third-party scripts with better performance, privacy, security and DX. Includes many popular third-parties out of the box. [Get started](https://scripts.nuxt.com/docs/getting-started) [Star on GitHub](https://github.com/nuxt/scripts) [Ahrefs Web Analytics](https://scripts.nuxt.com/scripts/ahrefs-analytics) [Plausible Analytics](https://scripts.nuxt.com/scripts/plausible-analytics) [Cloudflare Web Analytics](https://scripts.nuxt.com/scripts/cloudflare-web-analytics) [Vercel Analytics](https://scripts.nuxt.com/scripts/vercel-analytics) [PostHog](https://scripts.nuxt.com/scripts/posthog) [Fathom Analytics](https://scripts.nuxt.com/scripts/fathom-analytics) [Matomo Analytics](https://scripts.nuxt.com/scripts/matomo-analytics) [Rybbit Analytics](https://scripts.nuxt.com/scripts/rybbit-analytics) [Databuddy Analytics](https://scripts.nuxt.com/scripts/databuddy-analytics) [Segment](https://scripts.nuxt.com/scripts/segment) [Mixpanel](https://scripts.nuxt.com/scripts/mixpanel-analytics) [![Pulse Analytics logo](https://cdn.ciphera.net/pulse/pulse_icon_64.png)](https://scripts.nuxt.com/scripts/pulse-analytics "Pulse Analytics") [Bing UET](https://scripts.nuxt.com/scripts/bing-uet) [Meta Pixel](https://scripts.nuxt.com/scripts/meta-pixel) [X Pixel](https://scripts.nuxt.com/scripts/x-pixel) [TikTok Pixel](https://scripts.nuxt.com/scripts/tiktok-pixel) [Snapchat Pixel](https://scripts.nuxt.com/scripts/snapchat-pixel) [Reddit Pixel](https://scripts.nuxt.com/scripts/reddit-pixel) [LinkedIn Insight Tag](https://scripts.nuxt.com/scripts/linkedin-insight) [Google Adsense](https://scripts.nuxt.com/scripts/google-adsense) [Intercom](https://scripts.nuxt.com/scripts/intercom) [Hotjar](https://scripts.nuxt.com/scripts/hotjar) [![Clarity logo](https://store-images.s-microsoft.com/image/apps.29332.512b1d3d-80ec-4aec-83bb-411008d2f7cd.76371b6f-9386-463f-bfb0-b75cffb86a4f.bd99f4b1-b18e-4380-aa79-93768763c90d.png)](https://scripts.nuxt.com/scripts/clarity "Clarity") [Stripe](https://scripts.nuxt.com/scripts/stripe) [Lemon Squeezy](https://scripts.nuxt.com/scripts/lemon-squeezy) [PayPal](https://scripts.nuxt.com/scripts/paypal) [Vimeo Player](https://scripts.nuxt.com/scripts/vimeo-player) [YouTube Player](https://scripts.nuxt.com/scripts/youtube-player) ## **Better Web Vitals** Load scripts when they're needed with best practices non-blocking the rendering of your Nuxt app by default. ## **Privacy for your users** Avoid leaking user data to third-party scripts that don't need it. Ensure your scripts are GDPR compliant. ## **Secure third-parties** Protect your app from third-party scripts that could be compromised. ## **A powerful API with ***just enough*** magic ** Nuxt Scripts provides an abstraction layer on top of third-party scripts, providing SSR support and type-safety while still giving you full low-level control over how a script is loaded. app.vue ``` const { proxy } = useScript('/tracker.js', { trigger: 'onNuxtReady', use() { return { trackEvent: window.trackEvent } } }) // fully typed, SSR safe proxy.trackEvent('page_view') ``` ## Speed up with Facade Components Nuxt Scripts provides several [~~Facade Components ~~](https://developer.chrome.com/docs/lighthouse/performance/third-party-facades) out of the box. Facade components are fake UI elements that get replaced once a third-party script loads, they can significantly improve your performance while still providing a great user experience, however they do have [~~trade-offs ~~](https://scripts.nuxt.com/docs/guides/facade-components). ### **Google Maps** **Nuxt Scripts****11.9x Faster ** 70ms **Iframe** 830ms ### **YouTube** **Nuxt Scripts****46.4x Faster ** 70ms **Iframe** 3.3s ### **Vimeo** **Nuxt Scripts****3.7x Faster ** 70ms **Iframe** 260ms ### **Intercom** **Nuxt Scripts****3.9x Faster ** 220ms **Iframe** 850ms \*Note that PageSpeed Insights lab data is a snapshot from a particular day, which tends to be variable. ## **Cookie consent, typed per vendor ** Every consent-aware script returns a `consent` object matching the vendor's native API, paired with `defaultConsent` applied before the first call. Google Consent Mode v2, Meta grant/revoke, Matomo give/forget, TikTok three-state and more. Bridge OneTrust or Cookiebot, or ship your own banner with [~~useScriptTriggerConsent ~~](https://scripts.nuxt.com/docs/guides/consent). consent.ts ``` const { consent } = useScriptGoogleAnalytics({ id: 'G-XXXXXXXX', defaultConsent: { ad_storage: 'denied', analytics_storage: 'denied', }, }) // Google Consent Mode v2 consent.update({ ad_storage: 'granted', analytics_storage: 'granted', }) ``` ## **Privacy-first social embeds ** Embed posts from X, Instagram, and Bluesky without loading third-party scripts. All content is fetched server-side and proxied through your domain. Zero client-side JavaScript, zero cookies, zero third-party requests. Privacy and performance comparison between Nuxt Scripts and native social embeds | **Nuxt Scripts vs Native ** | X | Instagram | Bluesky | | --- | --- | --- | --- | | **Client JS ** | **0**vs475 KB | **0**vs580 KB | **0**vs42 KB | | **Requests ** | **0**vs25 | **0**vs34 | **0**vs9 | | **Cookies ** | **No**vsYes | **No**vsYes | **No**vsNo | | **Blocking Time ** | **0ms**vs1,010ms | **0ms**vs780ms | **0ms**vs60ms | ### Live social embed demos Live social demos load when this section approaches the viewport. ## **Watch the intro videos from the pros. ** ![Play video](https://i.ytimg.com/vi_webp/sjMqUUvH9AE/hq720.webp) ![Play video](https://i.ytimg.com/vi_webp/jDQtxlRUf54/hq720.webp) ## A faster web collaboration Nuxt Scripts was designed and built by the Nuxt core team in collaboration with the [~~Chrome Aurora~~](https://developer.chrome.com/aurora) team at Google. Nuxt Scripts is being actively maintained by the Nuxt core team and amazing community contributors, we welcome all contributions. ## **Community Funded ** Nuxt Scripts is free and open-source thanks to generous community sponsors. [**Become a sponsor **](https://github.com/sponsors/harlan-zw) The sponsor list is unavailable right now. ## **Try out our JS Confetti Tutorial ** Learn all of the fundamentals of Nuxt Scripts in the fun interactive confetti tutorial. [**Get started **](https://scripts.nuxt.com/docs/getting-started/confetti-tutorial) --- - **Page:** Carbon Ads - **Source:** https://scripts.nuxt.com/scripts/v0/ads/carbon-ads - **Description:** Show carbon ads in your Nuxt app using a Vue component. [Carbon Ads](https://www.carbonads.net/) is an ad service that provides a performance friendly way to show ads on your site. Nuxt Scripts provides a headless `ScriptCarbonAds` component to embed Carbon Ads in your Nuxt app. ## ScriptCarbonAds The `ScriptCarbonAds` component works differently to other Nuxt Scripts component and does not rely on `useScript`, instead it simply inserts a script tag into the div of the component on mount. By default, the component uses CarbonAds best practices which is to load immediately on mount. You can make use of [Element Event Triggers](/docs/guides/script-triggers#element-event-triggers) if you want to load the ads on a specific event. ```vue ``` ### Handling Ad-blockers You can use these hooks to add a fallback when CarbonAds is blocked. ```vue ``` ### Adding UI The component renders as headless, meaning there is no inherit styles. If you'd like to customize the look of the ad, you can use this example from [nuxt.com](http://nuxt.com). ```vue ``` ### Component API See the [Facade Component API](/docs/guides/facade-components#facade-components-api) for full props, events, and slots. Note: The Carbon Ads script *does not* extend the `useScript` composable. Accessing the script will return the `HTMLScriptElement`. ### Props The `ScriptCarbonAds` component accepts the following props: - `serve`: The serve URL provided by Carbon Ads. - `placement`: The placement ID provided by Carbon Ads. - `format`: Format provided by Carbon Ads. --- - **Page:** Plausible Analytics - **Source:** https://scripts.nuxt.com/scripts/plausible-analytics - **Description:** Load Plausible's site-specific tracker, including custom self-hosted endpoints. [Plausible Analytics](https://plausible.io/) is a privacy-focused web analytics platform. This registry entry supports its current site-specific script. ::script-stats :: ::script-docs :: ### Self-hosted Plausible If you use a self-hosted version of Plausible, provide both the script URL and the event endpoint. Changing the script URL alone does not change the endpoint passed to Plausible. ```ts useScriptPlausibleAnalytics({ scriptId: 'YOUR_SCRIPT_ID', endpoint: 'https://my-self-hosted-plausible.io/api/event', scriptInput: { src: 'https://my-self-hosted-plausible.io/js/script.js' } }) ``` For Plausible Cloud, find the `scriptId` under **Site Installation** in your site settings. Plausible's [script update guide](https://plausible.io/docs/script-update-guide) explains the site-specific URL and `plausible.init()`{lang="ts"} options. ### Extract a Script ID A current Plausible installation tag looks like this: ```html ``` Your `scriptId` is the part after `pa-` and before `.js`: ```ts scriptId: 'gYyxvZhkMzdzXBAtSeSNz' // ^^^^^^^^^^^^^^^^^^^^^^^ // Extract from: pa-{scriptId}.js ``` ::script-types :: --- - **Page:** v1.0.0 - **Source:** https://scripts.nuxt.com/docs/releases/v1 - **Description:** Nuxt Scripts v1 adds a first-party proxy, Partytown workers, server-rendered social embeds, SRI hashes, and registry scripts. Nuxt Scripts v1 is the project's first stable release. It adds a first-party proxy, per-script Partytown support, server-rendered social embeds, consent APIs, and new registry integrations. ## Highlights ### First-Party Mode Loading a third-party script gives its provider browser data that it can use for [fingerprinting](https://developer.mozilla.org/en-US/docs/Glossary/Fingerprinting). The amount of data varies by provider. The [Nuxt Scripts audit snapshot](https://github.com/nuxt/scripts/blob/main/packages/script/src/script-sizes.json) records 5 X Pixel cookies and requests to 3 domains, plus browser signals such as battery status, CPU count, screen data, timezone, language, platform, and touch capability. It records Clarity traffic across 3 domains. First-party mode bundles supported scripts at build time and serves them from your domain. Its reverse proxy forwards supported runtime requests through your server and anonymizes fields such as IP addresses (`180.233.124.74` โ†’ `180.233.124.0`) and browser versions (`Mozilla/5.0 (compatible; Chrome/120.0)`{lang="ts"}). Registry scripts use these capabilities by default when they declare support for them. Serving bundled scripts from your origin avoids DNS and connection setup for the original script host. Same-origin proxy URLs are also less likely to match host-based blocking rules, although other blocking rules can still identify them. First-party mode does not remove consent requirements or the need for a consent banner where the law requires one. See the [First-Party Mode Guide](/docs/guides/first-party) and [PR #577](https://github.com/nuxt/scripts/pull/577) for details. ### Partytown Web Worker Support Set `partytown: true` to run a supported registry script in a [Partytown](https://partytown.qwik.dev/) web worker. Its first-party bundling and proxy capabilities still apply. See [PR #576](https://github.com/nuxt/scripts/pull/576). The [current Partytown quick path](https://github.com/nuxt/scripts/blob/main/packages/script/src/runtime/composables/useScript.ts) renders the script into the initial HTML and returns a loaded stub. It bypasses trigger resolution, `use()`{lang="ts"} proxies, `load()`{lang="ts"}, and `reload()`{lang="ts"}. In the configuration below, `trigger` enables generation of each global registry instance, but `onNuxtReady` does not delay the Partytown script. Nuxt Scripts configures forwarding for `googleAnalytics`, `plausibleAnalytics`, `cloudflareWebAnalytics`, `umamiAnalytics`, `matomoAnalytics`, `segment`, `mixpanelAnalytics`, `clarity`, `bingUet`, `metaPixel`, `xPixel`, `tiktokPixel`, `snapchatPixel`, `redditPixel`, `linkedinInsight`, and `calendly`. Do not assume that forwarding alone makes every registry integration usable on the current quick path. For example, Plausible and Umami need attributes that this path drops, while Clarity depends on a client initialization callback that never runs. ::callout{icon="i-heroicons-exclamation-triangle" color="amber"} The Partytown tracker has an [open report of dropped GA4 sessions](https://github.com/QwikDev/partytown/issues/583) when Google Tag Manager runs in a worker. Nuxt Scripts does not declare Partytown support for GTM. Test analytics delivery before moving GA4 to Partytown. :: ### Server-Rendered Social Embeds Twitter widgets, Instagram embeds, and Bluesky posts normally load vendor scripts in the browser. Following the [Cloudflare Zaraz approach](https://blog.cloudflare.com/zaraz-supports-server-side-rendering-of-embeds/), Nuxt Scripts can fetch the embed data on the server and rewrite supported media URLs through your domain. See [PR #590](https://github.com/nuxt/scripts/pull/590). ```vue ``` ### Consent Controls v1 adds two layers of consent control. See the [Consent Guide](/docs/guides/consent), [PR #544](https://github.com/nuxt/scripts/pull/544), [PR #631](https://github.com/nuxt/scripts/pull/631), and [PR #712](https://github.com/nuxt/scripts/pull/712). **Consent trigger:** [`useScriptTriggerConsent()`{lang="ts"}](/docs/api/use-script-trigger-consent) prevents a script from loading until consent is granted. Its reactive state records later revocation, although revoking consent does not unload a script: ```ts const trigger = useScriptTriggerConsent() useScriptGoogleTagManager({ id: 'GTM-XXX', scriptOptions: { trigger } }) trigger.accept() // Grant โ†’ script loads trigger.revoke() // Set consented to false; does not unload an already-loaded script trigger.consented // Ref ``` **Vendor controls:** consent-aware registry scripts expose a `consent` object and a typed `defaultConsent` option that runs before the first tracking call. This covers Google Analytics, Google Tag Manager, Bing UET, Meta Pixel, TikTok Pixel, Matomo, Mixpanel, PostHog, and Clarity. The types preserve each vendor's vocabulary, including GCMv2 for Google and Bing, `grant`/`revoke` for Meta, and `'opt-in'`/`'opt-out'` defaults for Mixpanel and PostHog. Import the GCMv2 `ConsentState` from `#nuxt-scripts/types`{lang="ts"}. ```ts 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() ``` ### DevTools The Nuxt DevTools panel now covers the v1 privacy and capability model ([implementation commit](https://github.com/nuxt/scripts/commit/1c2855fa)): - **First-party panel:** proxy routes, active anonymization flags, and upstream domains. - **Registry panel:** support and activation status for `bundle`, `proxy`, and `partytown`. - **Script inspection:** status, load timing, network waterfall, bundle size, and inline documentation. - **Standalone view:** a separate DevTools tab connected to the Nuxt development server. ### New Registry Scripts - **PostHog Analytics** ([#568](https://github.com/nuxt/scripts/pull/568)): Product analytics with feature flags - **Google reCAPTCHA v3** ([#567](https://github.com/nuxt/scripts/pull/567)): Invisible bot protection - **TikTok Pixel** ([#569](https://github.com/nuxt/scripts/pull/569)): Conversion tracking - **Google Sign-In** ([#573](https://github.com/nuxt/scripts/pull/573)): One-tap authentication - **Rybbit Analytics** ([#453](https://github.com/nuxt/scripts/pull/453)): Privacy-focused open source analytics - **Databuddy Analytics** ([#495](https://github.com/nuxt/scripts/pull/495)): Lightweight analytics - **Bing UET** ([#650](https://github.com/nuxt/scripts/pull/650)): Microsoft Advertising conversion tracking - **Mixpanel Analytics** ([#648](https://github.com/nuxt/scripts/pull/648)): Product analytics and user tracking - **Vercel Analytics** ([#605](https://github.com/nuxt/scripts/pull/605)): Vercel Web Analytics integration - **Gravatar** ([#606](https://github.com/nuxt/scripts/pull/606)): Avatar service with privacy-preserving proxy ## Other Changes ### Script Reload API Scripts now expose a [`.reload()`{lang="ts"}](/docs/api/use-script) method for re-executing DOM-scanning scripts after SPA navigation. See [commit 77f853b](https://github.com/nuxt/scripts/commit/77f853b). ```ts const script = useScript('/third-party.js') await script.reload() ``` ### Subresource Integrity Hashes Set `assets.integrity` to generate [Subresource Integrity](https://developer.mozilla.org/en-US/docs/Web/Security/Defenses/Subresource_Integrity) hashes for bundled scripts. See [PR #575](https://github.com/nuxt/scripts/pull/575). ```ts export default defineNuxtConfig({ scripts: { assets: { integrity: 'sha384' } } }) ``` ### Script Stats Export The new `@nuxt/scripts/stats`{lang="ts"} subpath exports script privacy and performance data for audits; the [source lists its measured fields and rating inputs](https://github.com/nuxt/scripts/blob/main/packages/script/src/stats.ts). ```ts 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 - **Isolated player instances** ([#586](https://github.com/nuxt/scripts/pull/586)): Multiple players no longer share state - **Aspect ratio control**: New `ratio` prop - **Unmount cleanup**: Player instances are destroyed on unmount ### Vimeo Player - **Aspect ratio control** ([#624](https://github.com/nuxt/scripts/pull/624)): New `ratio` prop, matching YouTube Player API ### Google Maps The Google Maps integration now exposes Vue components for markers, shapes, overlays, clustering, static maps, and GeoJSON. **Declarative components** ([#510](https://github.com/nuxt/scripts/pull/510)): 11 components use Vue injection for parent-child communication and clean up on unmount. **Custom marker content** ([#658](https://github.com/nuxt/scripts/pull/658)): The `#content` slot on `ScriptGoogleMapsMarker` replaces the default pin with HTML or a Vue template. ```vue ``` **Custom overlay view** ([#658](https://github.com/nuxt/scripts/pull/658)): `ScriptGoogleMapsOverlayView` renders Vue content at a map position. Inside a marker, it inherits the position and follows the marker while dragging. Use `v-model:open` to toggle it without remounting. ```vue ``` **Direct `:position` prop:** Marker components accept `:position` without an options object. **Additional components:** - [**`ScriptGoogleMapsStaticMap`**](/scripts/google-maps/api/static-map) ([#673](https://github.com/nuxt/scripts/pull/673)): The static placeholder is now a standalone component. By default, images are routed through your server, which injects the API key into the upstream request. Google's current terms restrict storing and serving Static Maps images from an application server; the [billing guide](/scripts/google-maps/guides/billing) explains how to bypass this proxy with an explicit `api-key`. - [**`ScriptGoogleMapsGeoJson`**](/scripts/google-maps/api/geojson) ([#656](https://github.com/nuxt/scripts/pull/656)): Declarative wrapper around `google.maps.Data` for loading and styling GeoJSON with full event bindings. **Infrastructure:** - **Color mode support** ([#587](https://github.com/nuxt/scripts/pull/587)): Auto light/dark map switching with `mapIds` prop - **Geocode proxy**: Server-side geocoding hides API keys and caches results. Review the current caching restriction in the [billing guide](/scripts/google-maps/guides/billing) before using it. - **Resource cleanup** ([#651](https://github.com/nuxt/scripts/pull/651)): `useGoogleMapsResource` cleans up subcomponents on unmount, including across async boundaries - **Marker clustering** ([#517](https://github.com/nuxt/scripts/pull/517), [#653](https://github.com/nuxt/scripts/pull/653)): Batch operations use `noDraw` to avoid repeated renders **Deprecation**: v1 consolidates the legacy `ScriptGoogleMapsMarker` (wrapping `google.maps.Marker`) and `ScriptGoogleMapsAdvancedMarkerElement` names into `ScriptGoogleMapsMarker` (wrapping `google.maps.marker.AdvancedMarkerElement`). v1 removes `ScriptGoogleMapsPinElement`; a slot-passthrough compatibility shim remains, but custom marker markup belongs in the `#content` slot on `ScriptGoogleMapsMarker`. ### Environment-Variable Config The module now auto-populates `runtimeConfig.public.scripts` for fields that each registry integration declares as environment-backed. IDs, keys, and domains covered by those declarations resolve from `NUXT_PUBLIC_SCRIPTS_*` env vars without matching `runtimeConfig` boilerplate. See [PR #634](https://github.com/nuxt/scripts/pull/634). ```bash # .env NUXT_PUBLIC_SCRIPTS_GOOGLE_ANALYTICS_ID=G-XXXXXX NUXT_PUBLIC_SCRIPTS_POSTHOG_API_KEY=phc_xxx ``` ## Breaking Changes and Migration Upgrading from v0? See the [v0 to v1 migration guide](/docs/migration-guide/v0-to-v1) 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](https://github.com/nuxt/scripts/pull/585)) - `f8ce5a1` fix(gtm): invoke onBeforeGtmStart callback when ID is in config ([#584](https://github.com/nuxt/scripts/pull/584)) - `a8d20b0` fix: add `estree-walker` as a dependency ([#583](https://github.com/nuxt/scripts/pull/583)) - `4c79486` fix(plausible): use consistent window reference in clientInit stub ([#574](https://github.com/nuxt/scripts/pull/574)) - `78367b1` fix(matomo): respect user-provided URL protocol ([#572](https://github.com/nuxt/scripts/pull/572)) - [`c685f43`](https://github.com/nuxt/scripts/commit/c685f43) fix: broken type augmenting - `e2050a2` fix: align templates with existing augments ([#589](https://github.com/nuxt/scripts/pull/589)) - `da3a8cc` chore: include `.nuxt` types ([#588](https://github.com/nuxt/scripts/pull/588)) - `039380e` fix: prevent memory leaks in all Google Maps sub-components ([#651](https://github.com/nuxt/scripts/pull/651)) - `7e139b3` fix: avoid mutating runtimeConfig scriptOptions ([#638](https://github.com/nuxt/scripts/pull/638)) - `01b1af4` fix: expand self-closing ``{lang="html"} tags to prevent SFC extraction issues ([#613](https://github.com/nuxt/scripts/issues/613)) - `c3a6098` fix: preserve compressed/binary request bodies in proxy handler ([#619](https://github.com/nuxt/scripts/pull/619)) - `96db067` fix: missing Bing UET types ([#710](https://github.com/nuxt/scripts/pull/710)) - `fdaf089` fix(types): prevent registry entry types from collapsing to `never`{lang="ts"} ([#701](https://github.com/nuxt/scripts/pull/701)) - `698a585` fix: inherit registry scriptOptions in composable instances ([#691](https://github.com/nuxt/scripts/pull/691)) - `506dfd8` fix(google-maps): guard pan-on-open for closed/unpositioned overlay ([#698](https://github.com/nuxt/scripts/pull/698)) - `6fdd533` fix(google-maps): prevent center reset on re-render and export clusterer types ([#686](https://github.com/nuxt/scripts/pull/686)) - `0e4ae9e` fix(google-maps): prevent zoom/pan reset when overlay toggles ([#685](https://github.com/nuxt/scripts/pull/685)) - `33ebf61` fix(google-maps): close races in `resolveQueryToLatLng`{lang="ts"} ([#693](https://github.com/nuxt/scripts/pull/693)) - `4df13ed` fix: add `defineSlots`{lang="ts"} to all components for proper slot type inference ([#684](https://github.com/nuxt/scripts/pull/684)) - `afbb6bd` fix(types): broken IDE display of registry types ([#683](https://github.com/nuxt/scripts/pull/683)) - `63d4da5` fix: add v0 migration warnings and docs for breaking config changes ([#679](https://github.com/nuxt/scripts/pull/679)) - `3007458` fix(google-analytics): add `www.google.com`{lang="html"} and `www.googletagmanager.com`{lang="html"} to proxy domains ([#678](https://github.com/nuxt/scripts/pull/678)) - [`1c47e72`](https://github.com/nuxt/scripts/commit/1c47e72) fix: add missing proxy domains across registry scripts - `e6ecfff` fix(google-maps): bind `$attrs`{lang="ts"} to overlay view ([#672](https://github.com/nuxt/scripts/pull/672)) - `c2f8a8b` fix(google-maps): remove redundant slot props from OverlayView ([#666](https://github.com/nuxt/scripts/pull/666)) - `b65e5db` fix(google-maps): OverlayView, InfoWindow, and AdvancedMarkerElement DX issues ([#660](https://github.com/nuxt/scripts/pull/660)) - `a597de9` fix: prevent memory leak in `ScriptGoogleMapsAdvancedMarkerElement`{lang="html"} ([#649](https://github.com/nuxt/scripts/pull/649)) - `32ff4fa` fix: improve registry script DX and type safety ([#647](https://github.com/nuxt/scripts/pull/647)) - `569eeeb` fix(first-party): inject proxy endpoints for boolean/mock registry entries ([#640](https://github.com/nuxt/scripts/pull/640)) - `00fbaae` fix: allow custom script keys in `ScriptRegistry`{lang="ts"} type ([#632](https://github.com/nuxt/scripts/pull/632)) - [`3b93696`](https://github.com/nuxt/scripts/commit/3b93696) fix(plausible): broken firstParty mode - [`1c31a71`](https://github.com/nuxt/scripts/commit/1c31a71) fix(gravatar): broken proxy --- - **Page:** Ahrefs Web Analytics - **Source:** https://scripts.nuxt.com/scripts/ahrefs-analytics - **Description:** Track page views and custom events with Ahrefs Web Analytics in Nuxt. [Ahrefs Web Analytics](https://help.ahrefs.com/en/articles/10247870-about-ahrefs-web-analytics) tracks page views and custom events without cookies or persistent identifiers. It discards the raw IP address after deriving coarse location data. ::script-stats :: ::script-docs :: Default: - **Trigger: `onNuxtReady`** The script loads when the Nuxt app is ready. Use the proxy for void calls. Await `$script` when you need the loaded `AhrefsAnalytics` object. The [Ahrefs tracked events guide](https://help.ahrefs.com/en/articles/11381932-tracked-events-in-ahrefs-web-analytics) documents the JavaScript API and event-property shape. ::code-group ```ts [Proxy] const { proxy } = useScriptAhrefsAnalytics({ key: 'your-project-key', }) function trackSignup() { proxy.AhrefsAnalytics.sendEvent('signup', { props: { plan: 'pro' }, }) } ``` ```ts [onLoaded] const { onLoaded } = useScriptAhrefsAnalytics({ key: 'your-project-key', }) onLoaded(({ AhrefsAnalytics }) => { AhrefsAnalytics.sendEvent('signup', { props: { plan: 'pro' }, }) }) ``` :: ## SPA navigation Ahrefs Analytics tracks single-page app navigations natively: the loaded [`analytics.js`](https://analytics.ahrefs.com/analytics.js) patches `history.pushState` and listens for `popstate`, firing a fresh page view whenever the URL changes. Nuxt route changes need no extra configuration. ::script-types :: --- - **Page:** Cloudflare Web Analytics - **Source:** https://scripts.nuxt.com/scripts/cloudflare-web-analytics - **Description:** Load Cloudflare Web Analytics during hydration with its built-in SPA measurement enabled. [Cloudflare Web Analytics](https://developers.cloudflare.com/web-analytics/about/) reports traffic and Web Vitals. Its beacon [does not use cookies or local storage](https://developers.cloudflare.com/web-analytics/data-metrics/core-web-vitals/#information-collected), and the company says the product does not collect or use visitors' personal data. ::script-stats :: ::script-docs :: Default: - **Trigger: Client** The script loads during Nuxt hydration to keep Web Vitals metrics accurate. This registry fixes the trigger to `client`. A `scriptOptions.trigger` value passed by the caller is overwritten, so this integration cannot currently be deferred with a consent or element trigger. ::callout{type="warning"} The current runtime always enables SPA tracking, even though [Cloudflare documents `spa: false`](https://developers.cloudflare.com/web-analytics/get-started/web-analytics-spa/#disable-spa-analytics) as the switch for disabling it. Passing `spa: false` to this registry currently has no effect. :: ## Loading in app.vue Register the beacon in `app.vue`: ```vue [app.vue] ``` The analytics script creates a `window.__cfBeacon` object. Access it after the script loads: ```ts const { onLoaded } = useScriptCloudflareWebAnalytics({ token: '12ee46bf598b45c2868bbc07a3073f58', }) onLoaded(({ __cfBeacon }) => { console.log(__cfBeacon) }) ``` ::script-types :: --- - **Page:** Segment - **Source:** https://scripts.nuxt.com/scripts/segment - **Description:** Load Segment Analytics.js and queue page, event, identity, and group calls. [Segment](https://www.twilio.com/en-us/segment) routes events from your site to analytics, marketing, and data warehouse destinations. Use [`useScriptSegment()`{lang="ts"}](/scripts/segment){lang="ts"} to load Analytics.js and access its tracking methods. Segment's [Analytics.js quickstart](https://www.twilio.com/docs/segment/connections/sources/catalog/libraries/website/javascript/quickstart) explains write keys and the `page`, `track`, and `identify` calls exposed by the library. The client initializer queues one `page()`{lang="ts"} call before Analytics.js loads. Avoid sending another page call for the initial route unless you intend to count it twice. ::script-stats :: ::script-docs :: ::script-types :: --- - **Page:** Rybbit Analytics - **Source:** https://scripts.nuxt.com/scripts/rybbit-analytics - **Description:** Load hosted or self-hosted Rybbit Analytics and call its tracking API. [Rybbit Analytics](https://rybbit.com/) is an open-source web analytics platform. Its [tracking script documentation](https://rybbit.com/docs/script) covers the hosted and self-hosted script URLs. ::script-stats :: ::script-docs :: ### Self-hosted Rybbit Analytics Rybbit's official self-hosted snippet replaces `app.rybbit.io` in `/api/script.js`. The current composable appends `/script.js` to `analyticsHost`, so include `/api` in the value: ```ts useScriptRybbitAnalytics({ analyticsHost: 'https://your-rybbit-instance.com/api', siteId: 'YOUR_SITE_ID' }) ``` Passing only the origin currently requests `https://your-rybbit-instance.com/script.js`, which differs from Rybbit's documented path. ::callout{color="amber"} Rybbit now configures automatic page views, SPA navigation, outbound links, errors, session replay, and Web Vitals in the site dashboard. The current wrapper still emits older `data-*` attributes for those fields, which the current tracker documentation does not list. It also omits `data-debounce` when `debounce` is `0`, even though Rybbit documents zero as the way to disable debouncing. :: ::script-types :: --- - **Page:** Databuddy Analytics - **Source:** https://scripts.nuxt.com/scripts/databuddy-analytics - **Description:** Load Databuddy's browser SDK and configure its analytics and performance tracking. [Databuddy](https://www.databuddy.cc/docs/getting-started) provides web analytics, error tracking, and performance measurements through a browser SDK. ::script-stats :: ::script-docs :: ## CDN or self-hosted By default, the registry injects the [documented browser SDK](https://www.databuddy.cc/docs/sdk/vanilla-js) from `https://cdn.databuddy.cc/databuddy.js`. If you host the script yourself, pass `scriptUrl` to override the `src`. ```ts useScriptDatabuddyAnalytics({ scriptUrl: 'https://my-host/databuddy.js', clientId: 'YOUR_CLIENT_ID', }) ``` ::callout{color="amber"} First-party bundling rewrites the script URL, so the current integration also creates `window.databuddyConfig`. That fallback contains `clientId` plus truthy values for `apiUrl`, `disabled`, `trackScreenViews`, `trackPerformance`, and `trackSessions`. Other options remain only as `data-*` attributes, and explicit `false` values for those three tracking fields are not copied to the fallback config. Disable bundling if the bundled SDK does not apply the setting you need. :: ::script-types :: --- - **Page:** Fathom Analytics - **Source:** https://scripts.nuxt.com/scripts/fathom-analytics - **Description:** Load Fathom Analytics and track page views, goals, and custom events. [Fathom Analytics](https://usefathom.com/) tracks site traffic without collecting visitors' personal data. ::script-stats :: ::script-docs :: ## Proxying is not supported Unlike most analytics integrations in Nuxt Scripts, Fathom **cannot** be proxied (`proxy: true`). Fathom's bot detection uses the connecting source IP address. When beacons are proxied, they reach Fathom from your server's IP (typically a datacenter), and Fathom's bot detection ignores `X-Forwarded-For` from arbitrary servers, so every visitor gets flagged as a bot. This behavior and the resulting Nuxt Scripts change are documented in the [proxy-support fix](https://github.com/nuxt/scripts/pull/722). Fathom [stopped offering custom domains in March 2023](https://usefathom.com/changelog/mar2023-firewall-settings). Existing custom domains continue to work, but new customers cannot configure one. Bundling (`bundle: true`) **is** supported: the script is served from your origin, but beacons still go directly to `cdn.usefathom.com` from the browser so real client IPs reach Fathom's bot detection correctly. ## Defaults - **Trigger: `onNuxtReady`** The script loads when the Nuxt app is ready. Use the composable's `proxy` object for void calls. Use `onLoaded` when you need the loaded `fathom` object. ::code-group ```ts [Proxy] const { proxy } = useScriptFathomAnalytics() function trackMyGoal() { proxy.trackGoal('MY_GOAL_ID', 100) } ``` ```ts [onLoaded] const { onLoaded } = useScriptFathomAnalytics() onLoaded(({ trackGoal }) => { trackGoal('MY_GOAL_ID', 100) }) ``` :: ::script-types :: ## Example The default trigger waits until Nuxt is ready: ```vue [app.vue] ``` --- - **Page:** Pulse Analytics - **Source:** https://scripts.nuxt.com/scripts/pulse-analytics - **Description:** Load the Pulse tracker and record custom events. [Pulse](https://pulse.ciphera.net/) is cookie-free web analytics by [Ciphera](https://ciphera.net/). The tracker leaves nothing in the browser, no cookie and no stored identifier; Pulse works out who a visitor is on its own servers, from rotating hashes. It honours Do Not Track and Global Privacy Control without extra config. The [script reference](https://docs.ciphera.net/pulse/script-installation) lists every attribute this composable maps. ::script-stats :: ::script-docs :: ## Proxying is not supported Pulse builds visitor identity on its server from the connecting IP address and user agent. Route the beacons through your Nuxt server and they all arrive from one IP, so every visitor on the same user agent collapses into a single identity. Its [bot filtering](https://docs.ciphera.net/pulse/bot-filtering) counts a datacenter or hosting-provider origin as a signal too, which is where a proxied server sits. So Nuxt Scripts bundles the tracker and serves it from your origin, but its beacons go straight to the Pulse API. Nothing is lost client-side: the tracker keeps no identifier in the browser for a first-party proxy to shield. ## Self-hosted or proxied API `apiUrl` sets the origin the tracker posts to. Leave it unset for the hosted Pulse API. ```ts useScriptPulseAnalytics({ domain: 'YOUR_DOMAIN', apiUrl: 'https://pulse-api.example.com', }) ``` ## Custom events Use the composable's `proxy` object for `track` calls. Call it before the script has loaded and Nuxt Scripts holds the call, then replays it once the tracker is in. If the visitor has opted out, the tracker never runs and the queue is dropped. ::code-group ```ts [Proxy] const { proxy } = useScriptPulseAnalytics() function trackSignup() { proxy.track('signup', { plan: 'pro' }) } ``` ```ts [onLoaded] const { onLoaded } = useScriptPulseAnalytics() onLoaded(({ track }) => { track('purchase', { product: 'annual_plan' }, 99) }) ``` :: Event names may contain letters, numbers and underscores. Property values must be strings, and the optional third argument is revenue. See the [custom events reference](https://docs.ciphera.net/pulse/custom-events). ::script-types :: ## Example The default trigger waits until Nuxt is ready: ```vue [app.vue] ``` --- - **Page:** Matomo Analytics - **Source:** https://scripts.nuxt.com/scripts/matomo-analytics - **Description:** Track Matomo page views and events with built-in consent controls. [Matomo Analytics](https://matomo.org/) tracks page views and events with either Matomo Cloud or a self-hosted instance. ::script-stats :: ::script-docs :: The `watch` option defaults to `true`. The composable registers its `useScriptEventPage` watcher immediately, independently of the script trigger. Register the composable before the initial `page:finish` hook to track that route and later navigations; registering it afterward only catches subsequent navigations. Pass `siteId` explicitly: the current runtime does not apply its intended fallback of `1` when the option is omitted. ```ts useScriptMatomoAnalytics({ cloudId: 'YOUR_CLOUD_ID', // e.g. nuxt.matomo.cloud siteId: 2, // watch: true, // Enabled by default; tracks pages automatically. }) ``` Push commands to `_paq` for custom dimensions or manual events: ```ts const { proxy } = useScriptMatomoAnalytics({ cloudId: 'YOUR_CLOUD_ID', // e.g. nuxt.matomo.cloud siteId: 2, }) // set custom dimension proxy._paq.push(['setCustomDimension', 1, 'value']) // send page event proxy._paq.push(['trackPageView']) ``` See the [Config Schema](#config-schema) for the full option list. ## Custom page tracking Provide `watch: false` to disable the built-in page watcher, then queue the initial route yourself if you need it: ```ts const { proxy } = useScriptMatomoAnalytics({ cloudId: 'YOUR_CLOUD_ID', siteId: 2, watch: false, // disable automatic tracking }) // watch: false disables the built-in page watcher. proxy._paq.push(['trackPageView']) // Custom page tracking with additional logic useScriptEventPage((payload) => { // Set custom dimensions based on route if (payload.path.startsWith('/products')) { proxy._paq.push(['setCustomDimension', 1, 'Product Page']) } // Standard Matomo tracking calls (same as built-in watch behavior) proxy._paq.push(['setDocumentTitle', payload.title]) proxy._paq.push(['setCustomUrl', payload.path]) proxy._paq.push(['trackPageView']) // Track additional custom events proxy._paq.push(['trackEvent', 'Navigation', 'PageView', payload.path]) }) ``` ### Using self-hosted Matomo For self-hosted Matomo, set `matomoUrl` to customize tracking. Set `trackerUrl` as well if you use a custom tracking endpoint. ```ts useScriptMatomoAnalytics({ // For example, https://your-url.com/matomo.js and /matomo.php both exist. matomoUrl: 'https://your-url.com', siteId: 2, }) ``` ## Consent mode Matomo has a built-in [tracking-consent API](https://developer.matomo.org/guides/tracking-consent) gated by `requireConsent`. Set `defaultConsent` to arm the gate at registration, then call `consent.give()`{lang="ts"} / `consent.forget()`{lang="ts"} at runtime. ### `defaultConsent` | Value | Behavior | | ---------------- | ------------------------------------------------------------------------------------ | | `'required'` | Pushes `['requireConsent']`. Matomo tracks nothing until the user opts in. | | `'given'` | Pushes `['requireConsent']` then `['setConsentGiven']`. Tracking starts immediately. | | `'not-required'` | Default Matomo behavior (no consent gating). | ::callout{icon="i-heroicons-exclamation-triangle" color="warning"} `consent.give()`{lang="ts"} and `consent.forget()`{lang="ts"} are **no-ops unless `defaultConsent: 'required'` or `'given'` was set at registration**. Matomo ignores `setConsentGiven` and `forgetConsentGiven` when `requireConsent` hasn't been pushed. A development-only warning fires if you forget. :: ### Example ```vue ``` ### Using white-label Matomo For a white-label Matomo deployment, set `trackerUrl` and `scriptInput.src` to customize tracking. ```ts useScriptMatomoAnalytics({ siteId: 2, trackerUrl: 'https://c.staging.cookie3.co/lake', scriptInput: { src: 'https://cdn.cookie3.co/scripts/latest/cookie3.analytics.min.js', }, }) ``` ::script-types :: --- - **Page:** Vercel Analytics - **Source:** https://scripts.nuxt.com/scripts/vercel-analytics - **Description:** Load Vercel Web Analytics and send page views or custom events. [Vercel Web Analytics](https://vercel.com/docs/analytics) records page views and custom events. Its [Privacy and Compliance](https://vercel.com/docs/analytics/privacy-policy) guide documents the collection and visitor identification model. ::script-stats :: ::script-docs :: ### Non-Vercel Deployment When deploying outside [Vercel](https://vercel.com), provide your DSN explicitly. Its [analytics package reference](https://vercel.com/docs/analytics/package) documents the `dsn` option for this case: ```ts useScriptVercelAnalytics({ dsn: 'YOUR_DSN', }) ``` ### First-Party Mode This registry entry enables first-party mode. Nuxt bundles the script locally and proxies intake requests through your server. The proxy anonymizes the client IP to a subnet, but it does not redact event bodies or URLs. ```ts export default defineNuxtConfig({ scripts: { registry: { vercelAnalytics: { trigger: 'client' }, } } }) ``` ## Defaults - **Trigger: Client** The script loads during Nuxt hydration to keep Web Vitals metrics accurate. The registry fixes this trigger to `client`. A `scriptOptions.trigger` value passed by the caller is overwritten. The build environment selects the file: development builds use `script.debug.js`, while production builds use `script.js`. The `mode` option sets Vercel's `window.vam` runtime value; it does not change that file selection. The current `debug` mapping only forwards `debug: false` in development, so `debug: true` does not enable the debug script in a production build. Call the void-returning `track` and `pageview` methods through the proxy. You can also await `$script` to access the loaded object. ::code-group ```ts [Proxy] const { proxy } = useScriptVercelAnalytics() proxy.track('signup', { plan: 'pro' }) ``` ```ts [onLoaded] const { onLoaded } = useScriptVercelAnalytics() onLoaded(({ track }) => { track('signup', { plan: 'pro' }) }) ``` :: ::script-types :: ## Example Load Vercel Analytics through `app.vue`: ```vue [app.vue] ``` ### Manual Tracking Disable automatic tracking and call `track` or `pageview` yourself: ```vue [app.vue] ``` ### beforeSend Use `beforeSend` to filter or modify events before they reach Vercel. Return `null` to cancel an event. This is also where Vercel recommends [redacting sensitive URL data](https://vercel.com/docs/analytics/redacting-sensitive-data). ```vue [app.vue] ``` --- - **Page:** PostHog - **Source:** https://scripts.nuxt.com/scripts/posthog - **Description:** Load posthog-js with first-party proxy support, feature flags, and opt-in or opt-out controls. [PostHog](https://posthog.com) is an open-source product analytics platform with session replay, feature flags, and experiments. Nuxt Scripts loads PostHog's official [`posthog-js` browser SDK](https://posthog.com/docs/libraries/js) from [npm](https://www.npmjs.com/). Use [`useScriptPostHog()`{lang="ts"}](/scripts/posthog){lang="ts"} to load the SDK and access the PostHog client. ::script-stats :: ::script-docs :: ## Installation You must install the `posthog-js` dependency: ```bash pnpm add posthog-js ``` ## EU Hosting To use PostHog's EU cloud: ```ts export default defineNuxtConfig({ scripts: { registry: { posthog: { apiKey: 'YOUR_API_KEY', region: 'eu' } } } }) ``` ## First-Party Proxy When [first-party mode](/docs/guides/first-party) is active (auto-enabled for scripts that support it), your server automatically proxies PostHog requests. PostHog [recommends a reverse proxy](https://posthog.com/docs/advanced/proxy) for more reliable event capture because ad blockers can reject requests to known analytics domains. Nuxt anonymizes the client IP address to subnet level; other data passes through so features such as session replay and feature flags continue to work. The module sets `apiHost` to your server's proxy endpoint: ```ts export default defineNuxtConfig({ scripts: { registry: { posthog: { apiKey: 'YOUR_API_KEY', // apiHost is auto-set to '/_scripts/p/ph' (or '/_scripts/p/ph-eu' for EU region) } } } }) ``` The proxy handles API requests and static assets, including the session recording SDK. ## Custom API Host To use a custom reverse proxy or self-hosted PostHog instance, set `apiHost` directly: ```ts export default defineNuxtConfig({ scripts: { registry: { posthog: { apiKey: 'YOUR_API_KEY', apiHost: '/my-proxy' } } } }) ``` The `apiHost` option accepts any URL or relative path, overriding both the `region` default and the first-party proxy auto-configuration. For additional PostHog SDK options like `ui_host`, use the `config` passthrough. ## Feature Flags Feature flag methods return values, so you need to wait for PostHog to load first: ```ts const { onLoaded } = useScriptPostHog() onLoaded(({ posthog }) => { // Check a feature flag if (posthog.isFeatureEnabled('new-dashboard')) { // Show new dashboard } // Get flag payload const payload = posthog.getFeatureFlagPayload('experiment-config') }) ``` ## Consent Mode PostHog exposes [`opt_in_capturing` / `opt_out_capturing`](https://posthog.com/docs/libraries/js#opt-out-of-data-capture). Set the boot-time default with `defaultConsent` and call `consent.optIn()`{lang="ts"} / `consent.optOut()`{lang="ts"} at runtime. ### `defaultConsent` | Value | Behavior | | ----------- | -------------------------------------------------------------------------------------------------------- | | `'opt-in'` | Calls `posthog.opt_in_capturing()`{lang="ts"} immediately after init. | | `'opt-out'` | Calls `posthog.init(..., { opt_out_capturing_by_default: true })`{lang="ts"} so the SDK boots opted out. | ::callout{icon="i-heroicons-information-circle"} Use `defaultConsent: 'opt-out'` when you need the SDK to boot opted out. The runtime `consent.optOut()`{lang="ts"} calls `opt_out_capturing()`{lang="ts"} **after** init, which is weaker than the boot-time flag; any events captured between init and the opt-out call are still sent. :: ### Example ```vue ``` Configuring PostHog globally in `nuxt.config`: ```ts export default defineNuxtConfig({ scripts: { registry: { posthog: { apiKey: 'YOUR_API_KEY', defaultConsent: 'opt-out', } } } }) ``` ## Disabling Session Recording ```ts export default defineNuxtConfig({ scripts: { registry: { posthog: { apiKey: 'YOUR_API_KEY', disableSessionRecording: true } } } }) ``` ::script-types :: --- - **Page:** Mixpanel - **Source:** https://scripts.nuxt.com/scripts/mixpanel-analytics - **Description:** Load Mixpanel and track product events, identities, profiles, and consent. [Mixpanel](https://mixpanel.com) analyzes product events through funnels and retention reports. [`useScriptMixpanelAnalytics()`{lang="ts"}](/scripts/mixpanel-analytics) initializes the SDK and exposes the `mixpanel` API. ::script-stats :: ::script-docs :: ::script-types :: ## Examples ### Tracking Events ```vue ``` ### Identifying Users ```vue ``` ### Registering Super Properties Mixpanel [adds registered super properties](https://docs.mixpanel.com/docs/tracking-methods/sdks/javascript#setting-super-properties) to subsequent events: ```vue ``` ### Resetting identity on logout Call `reset()`{lang="ts"} when an identified user signs out or their session expires. Mixpanel [recommends resetting at logout](https://docs.mixpanel.com/docs/tracking-methods/id-management/identifying-users-simplified#client-side-identity-management) so two people sharing a device are not merged into the same identity: ```ts const { proxy } = useScriptMixpanelAnalytics() function logout() { // End your app session first. proxy.mixpanel.reset() } ``` ## Consent Mode Mixpanel exposes [`opt_in_tracking` / `opt_out_tracking`](https://docs.mixpanel.com/docs/tracking-methods/sdks/javascript#opt-out-of-tracking). Set the boot-time default with `defaultConsent` and call `consent.optIn()`{lang="ts"} / `consent.optOut()`{lang="ts"} at runtime. ### `defaultConsent` | Value | Behavior | | ----------- | -------------------------------------------------------------------------------------------------------- | | `'opt-in'` | Starts opted in. | | `'opt-out'` | Calls `mixpanel.init(..., { opt_out_tracking_by_default: true })`{lang="ts"} so the SDK boots opted out. | ::callout{icon="i-heroicons-information-circle"} `defaultConsent: 'opt-out'` applies before the first event. Calling `consent.optOut()`{lang="ts"} later cannot retract events already captured by the SDK. :: ### Example ```vue ``` --- - **Page:** Bing UET - **Source:** https://scripts.nuxt.com/scripts/bing-uet - **Description:** Load Microsoft Advertising UET, send conversion events, and update its ad-storage consent signal. [Microsoft Advertising UET](https://about.ads.microsoft.com/en/tools/performance/conversion-tracking) tracks conversions and supplies audience data to Microsoft Advertising. [`useScriptBingUet()`{lang="ts"}](/scripts/bing-uet) loads the UET tag and exposes its event queue. ::script-stats :: ::script-docs :: ::script-types :: ## Examples ### Tracking Conversions Microsoft recommends the newer event syntax, which replaces short parameters such as `gv` and `gc` with [`revenue_value` and `currency`](https://learn.microsoft.com/en-us/advertising/msa-help/hlp_ba_conc_uetv2syntaxupdate_2). ```vue ``` ### Custom Events ```vue ``` ### Consent Mode Bing UET supports [advanced consent mode](https://learn.microsoft.com/en-us/advertising/msa-help/hlp_ba_conc_uet_consent). Only `ad_storage` is honored; set the initial state with `defaultConsent` and update it at runtime with `consent.update()`{lang="ts"}: ```vue ``` Microsoft requires sites using consent mode to send either `granted` or `denied` on each page. Its current enforcement covers the EEA, United Kingdom, and Switzerland. `onBeforeUetStart` remains available for any other pre-load setup. --- - **Page:** Meta Pixel - **Source:** https://scripts.nuxt.com/scripts/meta-pixel - **Description:** Load Meta Pixel, send events through fbq, and manage its binary consent state. [Meta Pixel](https://www.facebook.com/business/tools/meta-pixel) sends conversion and audience events to Meta Ads. [`useScriptMetaPixel()`{lang="ts"}](/scripts/meta-pixel){lang="ts"} loads the pixel and exposes the `fbq` queue. ::script-stats :: ::script-docs :: ::script-types :: ## Disabling automatic page views By default, Meta Pixel tracks a page view during initialization. Disable it when you send your own `PageView` with an `eventID` for Conversions API deduplication: ```ts useScriptMetaPixel({ id: 'YOUR_PIXEL_ID', trackPageView: false, }) ``` Then send the page view yourself and share the `eventID` with the Conversions API call: ```ts const { proxy } = useScriptMetaPixel({ id: 'YOUR_PIXEL_ID', trackPageView: false }) proxy.fbq('track', 'PageView', {}, { eventID: 'shared-with-capi' }) ``` ## Consent Mode Meta Pixel exposes a binary consent toggle. Set the initial state with `defaultConsent` (fires `fbq('consent', 'grant'|'revoke')`{lang="ts"} before `fbq('init', id)`{lang="ts"}) and call `consent.grant()`{lang="ts"} / `consent.revoke()`{lang="ts"} at runtime: ```vue ``` `defaultConsent: 'denied'` queues a revoke command before pixel initialization, but it does not delay the SDK request. If your consent policy requires no request to Meta before opt-in, use a [binary load gate](/docs/guides/consent#binary-load-gate) for the script itself. --- - **Page:** X Pixel - **Source:** https://scripts.nuxt.com/scripts/x-pixel - **Description:** Send X Ads conversions with browser and server event deduplication. [X Pixel](https://business.x.com/en/help/campaign-measurement-and-analytics/conversion-tracking-for-websites) records website conversions and builds audiences for X Ads campaigns. Use [`useScriptXPixel()`{lang="ts"}](/scripts/x-pixel){lang="ts"} to load the pixel and access its `twq` API. ::script-stats :: ::script-docs :: ## Browser and server event deduplication When you send the same conversion from the browser and a server integration, give both events the same unique `conversion_id`. X uses that value to deduplicate the pair, as described in its [conversion-tracking guide](https://business.x.com/en/help/campaign-measurement-and-analytics/conversion-tracking-for-websites): ```ts const { proxy } = useScriptXPixel({ id: 'YOUR_PIXEL_ID' }) proxy.twq('event', 'YOUR_EVENT_ID', { conversion_id: order.id, value: order.total, currency: 'USD', contents: order.items.map(item => ({ content_id: item.id, num_items: item.quantity, })), }) ``` The current Nuxt Scripts type requires `contents` for every event. X lists it as an event parameter rather than a universal requirement; only its Dynamic Product Ads instructions require it for all events. Include it for now or narrow through a local wrapper if your event has no item data. ::script-types :: --- - **Page:** Snapchat Pixel - **Source:** https://scripts.nuxt.com/scripts/snapchat-pixel - **Description:** Load Snapchat Pixel and opt in to automatic or manual page-view events. [Snapchat Pixel](https://businesshelp.snapchat.com/s/article/snap-pixel-about){:target="_blank"} reports browser events to Snapchat Ads for conversion measurement. Use [`useScriptSnapchatPixel()`{lang="ts"}](/scripts/snapchat-pixel){lang="ts"} to load the pixel and access its `snaptr` API. ::script-stats :: ::script-docs :: ## Page views This integration does not send `PAGE_VIEW` by default. Enable the initialization event explicitly. Snapchat's [official Page View snippet](https://businesshelp.snapchat.com/articles/en_US/Knowledge/pixel-bigcommerce) uses the same `snaptr('track', 'PAGE_VIEW')`{lang="ts"} command: ```ts const { proxy } = useScriptSnapchatPixel({ id: 'YOUR_PIXEL_ID', trackPageView: true, }) // Send later page views when your tracking policy requires them. proxy.snaptr('track', 'PAGE_VIEW') ``` ::script-types :: --- - **Page:** Reddit Pixel - **Source:** https://scripts.nuxt.com/scripts/reddit-pixel - **Description:** Load Reddit Pixel and send page visits or conversion events through its typed API. [Reddit Pixel](https://business.reddithelp.com/s/article/reddit-pixel) helps you track conversions and build audiences for your Reddit advertising campaigns. Use [`useScriptRedditPixel()`{lang="ts"}](/scripts/reddit-pixel){lang="ts"} to load Reddit Pixel and access its `rdt` API. ::script-stats :: ::script-docs :: ## Initial page visit The client initializer queues `rdt('track', 'PageVisit')`{lang="ts"} once after it queues `init`. It does not send another page visit on Nuxt route changes. Add your own route tracking if each client-side navigation should count as a [`PageVisit`](https://business.reddithelp.com/articles/Knowledge/supported-conversion-events). ::script-types :: --- - **Page:** Hotjar - **Source:** https://scripts.nuxt.com/scripts/hotjar - **Description:** Load Hotjar and queue events, user attributes, and virtual page views. [Hotjar](https://help.hotjar.com/hc/en-us/articles/36820019634961-What-is-Hotjar) combines session recordings, heatmaps, surveys, and user-feedback tools. ::script-stats :: ::script-docs :: ## Events and user attributes Calls made through `proxy.hj` queue until the script is ready. Send an event when you want to filter recordings or heatmaps by a specific action: ```ts const { proxy } = useScriptHotjar() function recordSignup() { proxy.hj('event', 'signup_completed') } ``` Hotjar's [Events API reference](https://help.hotjar.com/hc/en-us/articles/36819965075473-Events-API-Reference) lists the event-name restrictions and filtering limits. Events do not accept properties. Use the Identify API for user attributes instead: ```ts const { proxy } = useScriptHotjar() proxy.hj('identify', user.id, { plan: user.plan, }) ``` Before sending attributes, review Hotjar's [Identify API privacy and data guidance](https://help.hotjar.com/hc/en-us/articles/36820006120721-Identify-API-Reference). Prefer a stable internal ID over an email address, and send only the fields you need. ::script-types :: --- - **Page:** Clarity - **Source:** https://scripts.nuxt.com/scripts/clarity - **Description:** Load Microsoft Clarity and manage its session-recording consent state. [Microsoft Clarity](https://learn.microsoft.com/en-us/clarity/setup-and-installation/about-clarity) records sessions and generates heatmaps for your site. ::script-stats :: ::script-docs :: ::script-types :: ## Consent mode The `defaultConsent` option and `consent.set()`{lang="ts"} wrap Clarity's deprecated [Consent API V1](https://learn.microsoft.com/en-us/clarity/setup-and-installation/clarity-consent-api-v1). Microsoft documents that API with a boolean value (or no value to grant consent). Although the current Nuxt Scripts schema also accepts `Record`{lang="html"}, Clarity does not document an object payload for V1. ```vue ``` For new integrations, call Consent API V2 through the script proxy, using Clarity's case-sensitive category names: ```ts const { proxy } = useScriptClarity({ id: 'YOUR_PROJECT_ID', }) proxy.clarity('consentv2', { ad_Storage: 'denied', analytics_Storage: 'denied', }) // Update after the user grants consent. proxy.clarity('consentv2', { ad_Storage: 'granted', analytics_Storage: 'granted', }) ``` `defaultConsent` and `consent.set()`{lang="ts"} do not issue `consentv2` commands. See [Clarity Consent API V2](https://learn.microsoft.com/en-us/clarity/setup-and-installation/clarity-consent-api-v2) for the current API. --- - **Page:** TikTok Pixel - **Source:** https://scripts.nuxt.com/scripts/tiktok-pixel - **Description:** Track TikTok conversions with consent, deduplication, and advanced matching. [TikTok Pixel](https://ads.tiktok.com/help/article/tiktok-pixel) reports browser events to TikTok Ads for conversion measurement and audiences. Use [`useScriptTikTokPixel()`{lang="ts"}](/scripts/tiktok-pixel){lang="ts"} to load the pixel and access its `ttq` API. ::script-stats :: ::script-docs :: The loader and collection requests go directly to TikTok to preserve visitor IDs and [vendor cookies](https://ads.tiktok.com/resources/help/article/using-cookies-with-tiktok-pixel?lang=en). Nuxt Scripts does not bundle TikTok Pixel or apply collection proxying and IP anonymization. Use a [consent trigger](/docs/guides/consent#binary-load-gate) to control when requests start. ## Disabling automatic page views By default, TikTok Pixel tracks a page view during initialization. Disable it in the composable call: ```ts useScriptTikTokPixel({ id: 'YOUR_PIXEL_ID', trackPageView: false, }) ``` ## Consent mode TikTok Pixel exposes a three-state consent API: grant, revoke, or hold (defer the decision). Set the initial state with `defaultConsent` and call `consent.grant()`{lang="ts"} / `consent.revoke()`{lang="ts"} / `consent.hold()`{lang="ts"} at runtime: ```vue ``` See the [TikTok cookie consent docs](https://business-api.tiktok.com/portal/docs?id=1739585600931842) for the full behavior. The initial consent command is queued before pixel initialization, but it does not delay the SDK request. If your policy requires no request to TikTok before opt-in, use a [consent trigger](/docs/guides/consent#binary-load-gate) for the script itself. ## Data-residency endpoint Set `region: 'us'` to load the Pixel SDK from `analytics.us.tiktok.com` instead of the global host: ```ts useScriptTikTokPixel({ id: 'YOUR_PIXEL_ID', region: 'us', }) ``` This option selects the SDK host only. It does not by itself establish that the rest of your tracking setup meets a data-residency or privacy requirement. ## Server-side event deduplication For the Pixel + Events API (CAPI) pattern, pass the same `event_id` on both the browser and server sides so TikTok deduplicates the pair: ```vue ``` See [TikTok's event-deduplication guide](https://ads.tiktok.com/help/article/event-deduplication?lang=en) for full rules. ## Testing browser events TikTok's [browser Pixel testing guide](https://ads.us.tiktok.com/help/article/test-tiktok-pixel-events-video-walkthrough?lang=en) uses the Test Events tab in Events Manager: enter the site URL, open it through the generated test flow, then perform the action you want to inspect. The current Nuxt Scripts type also accepts `test_event_code` in a fourth `track` argument and forwards it to the SDK. TikTok documents that field for the server-side Events API, not for the browser `ttq.track` signature, so do not rely on it for browser testing. ## Advanced matching Nuxt Scripts expects each identify field (`email`, `phone_number`, `external_id`, `first_name`, `last_name`, `city`, `state`, `country`, `zip_code`) as a 64-character SHA-256 hex digest. TikTok's [Advanced Matching guide](https://ads.tiktok.com/help/article/advanced-matching-web?lang=en) covers normalization and hashing. In development, the composable warns when a value does not look hashed. ```ts async function sha256(value: string) { const input = new TextEncoder().encode(value) const digest = await crypto.subtle.digest('SHA-256', input) return Array.from(new Uint8Array(digest), byte => byte.toString(16).padStart(2, '0')).join('') } const { proxy } = useScriptTikTokPixel({ id: 'YOUR_PIXEL_ID' }) proxy.ttq.identify({ email: await sha256('user@example.com'.trim().toLowerCase()), phone_number: await sha256('+15551234567'), }) ``` [`crypto.subtle.digest()`{lang="ts"}](https://developer.mozilla.org/en-US/docs/Web/API/SubtleCrypto/digest) is available only in a secure context in browsers. Localhost is treated as secure for development; deploy this example over HTTPS. ::script-types :: --- - **Page:** LinkedIn Insight Tag - **Source:** https://scripts.nuxt.com/scripts/linkedin-insight - **Description:** Track LinkedIn Ads conversions, enhanced matching, and SPA page views. The [LinkedIn Insight Tag](https://business.linkedin.com/marketing-solutions/insight-tag) tracks conversions and sends audience signals to LinkedIn Ads. [`useScriptLinkedInInsight()`{lang="ts"}](/scripts/linkedin-insight) registers the partner ID and exposes the `lintrk` command queue. ::script-stats :: ::script-docs :: ::script-types :: ## Examples ### Tracking a conversion ```vue ``` ### Per-event deduplication with the Conversions API When you also send conversions through LinkedIn's server-side Conversions API, pass the same `event_id` to both. LinkedIn discards the server-side duplicate and counts the Insight Tag event. See [LinkedIn deduplication](https://learn.microsoft.com/en-us/linkedin/marketing/conversions/deduplication?view=li-lms-2026-01). ```vue ``` ### Page-load conversion deduplication For dedup on the auto-fired page-view, set `eventId` at registration. The composable assigns `window._linkedin_event_id` *before* the Insight Tag base code runs, so the page-view URL includes `&eventId=โ€ฆ` automatically. ```vue ``` ### Enhanced matching with `setUserData` Pass plain email; the Insight Tag SHA-256 hashes it on-device. See [LinkedIn enhanced matching](https://www.linkedin.com/help/lms/answer/a6246095). ```vue ``` ### SPA virtual page views By default, the Insight Tag fires a page-view exactly once when the script loads, so SPA route changes go untracked. Opt in to per-route tracking with `enableAutoSpaTracking`: ```vue ``` When enabled, the composable suppresses the script's built-in automatic page view (with `window._wait_for_lintrk = true`) and fires `lintrk('track')`{lang="ts"} on Nuxt's `page:finish` hook. Under the standard ``{lang="html"} lifecycle, this produces one `/collect` beacon for each route, including the initial page. Keep-alive or overlapping page transitions can register multiple hooks, so call the composable once from a long-lived component in those setups. ### Multiple partner IDs If you need to push more than one Partner ID onto `window._linkedin_data_partner_ids`, pass an array. The composable promotes the first ID to the primary `_linkedin_partner_id` global. ```vue ``` --- - **Page:** Google AdSense - **Source:** https://scripts.nuxt.com/scripts/google-adsense - **Description:** Show Google AdSense ads in your Nuxt app. [Google AdSense](https://www.google.com/adsense/start/) serves Google ads on your site. Choose the API that matches the placement: - [`useScriptGoogleAdsense()`{lang="ts"}](/scripts/google-adsense){lang="ts"} loads the `adsbygoogle` queue. - ``{lang="html"} renders an ad unit. ::script-stats :: ::script-docs :: ## Where to find ``{lang="html"} (publisher ID) Find your [Google AdSense publisher ID](https://support.google.com/adsense/answer/2923881?hl=en) under **Account > Settings > Account information**. It appears as `pub-โ€ฆ` in your account and as `ca-pub-โ€ฆ` in ad code. Replace ``{lang="html"} below with that value. ::callout{icon="i-heroicons-light-bulb" to="https://adsense.google.com/start/" target="_blank"} Manage ad types and placements from the **Auto ads** settings in your AdSense dashboard. :: ## Site ownership verification ### Automatic meta tag insertion If you provide a `client`, Nuxt automatically inserts a **meta tag** on the page for Google to verify your site ownership. ::tabs :::div{label="Example" icon="i-heroicons-code-bracket-square"} ```ts [nuxt.config.ts] export default defineNuxtConfig({ scripts: { registry: { googleAdsense: { client: 'ca-pub-', // AdSense Publisher ID }, }, }, }) ``` ::: :::div{label="Output" icon="i-heroicons-magnifying-glass-circle"} ```html ``` ::: :: ### Adding `ads.txt` Google recommends adding an `ads.txt` file to identify the advertising systems authorized to sell your inventory and reduce counterfeit inventory. #### Steps 1. Create a new file: `public/ads.txt` 2. Add the following content: ```plaintext google.com, pub-, DIRECT, f08c47fec0942fa0 ``` 3. Replace ``{lang="html"} with your **AdSense Publisher ID**. ::callout{icon="i-heroicons-light-bulb" to="https://support.google.com/adsense/answer/12171612" target="_blank"} An `ads.txt` file does not replace AdSense's site review. After publishing it, check the file's status in your AdSense dashboard. :: ## Enabling Auto ads [Auto ads](https://support.google.com/adsense/answer/9261805?hl=en) let Google choose placements based on the page's layout, content, and existing ads. You can still control formats and exclude pages or areas in AdSense. ::tabs :::div{label="Example" icon="i-heroicons-code-bracket-square"} ```ts [nuxt.config.ts] export default defineNuxtConfig({ scripts: { registry: { googleAdsense: { client: 'ca-pub-', // AdSense Publisher ID autoAds: true, // Enable Auto Ads }, }, }, }) ``` ::: :::div{label="Output" icon="i-heroicons-magnifying-glass-circle"} ```html ``` ::: :: ## Using the [``{lang="html"}](/scripts/google-adsense){lang="html"} component ``{lang="html"} renders one ad unit: ```vue ``` ### Component Props | Prop | Description | | ---------------------------- | --------------------------------------------------------------------------------------- | | `data-ad-client` | Your **Google AdSense publisher ID** (`ca-pub-XXXXXXXXXX`). | | `data-ad-slot` | Your **Ad Slot ID** (available in AdSense dashboard). | | `data-ad-format` | Ad format type (`auto`, `rectangle`, `horizontal`, `vertical`, `fluid`, `autorelaxed`). | | `data-ad-layout` | Layout: `in-article`, or `image-top`, `image-side`, `text-only` for in-feed. | | `data-ad-layout-key` | Layout key generated by AdSense for an in-feed unit. | | `data-full-width-responsive` | **Set to `true`** to make the ad responsive. | #### Example using `data-ad-layout` Set `data-ad-layout` for an in-article unit: ```vue ``` #### Example using `data-ad-layout-key` Copy the layout key from the in-feed unit generated by AdSense: ```vue ``` ## Handling ad blockers Use the `error` slot for visitors whose ad blocker stops the script: ```vue ``` ## Using the [`useScriptGoogleAdsense()`{lang="ts"}](/scripts/google-adsense){lang="ts"} composable Use [`useScriptGoogleAdsense()`{lang="ts"}](/scripts/google-adsense){lang="ts"} when you need the `adsbygoogle` queue without an ad-unit component. ```ts export function useScriptGoogleAdsense( _options?: GoogleAdsenseInput ) {} ``` See the [Registry Scripts guide](/docs/guides/registry-scripts) for trigger and loading options. ::callout{icon="i-heroicons-light-bulb" to="https://support.google.com/adsense" target="_blank"} See the official **Google AdSense guide**. :: ::script-types :: --- - **Page:** Stripe - **Source:** https://scripts.nuxt.com/scripts/stripe - **Description:** Load Stripe.js programmatically or embed a deferred Stripe Pricing Table. [Stripe](https://stripe.com) provides payment APIs and hosted checkout components. Nuxt Scripts provides two Stripe features: - [`useScriptStripe()`{lang="ts"}](/scripts/stripe){lang="ts"} composable which loads the script `https://js.stripe.com/basil/stripe.js` by default. Use the `version` option to [pin a Stripe.js SDK release](https://docs.stripe.com/sdks/stripejs-versioning) (e.g. `acacia`, `clover`, `dahlia`). - `ScriptStripePricingTable` component for embedding a [Stripe Pricing Table](https://docs.stripe.com/payments/checkout/pricing-table) with `https://js.stripe.com/v3/pricing-table.js`. ::script-stats :: ::script-docs :: ## Types Install `@stripe/stripe-js` for full TypeScript support. ```bash pnpm add -D @stripe/stripe-js ``` ## Loading Globally Stripe recommends loading Stripe.js on every page so its [advanced fraud detection](https://docs.stripe.com/disputes/prevention/advanced-fraud-detection) can observe browsing behavior before checkout. ::code-group ```ts [Always enabled] export default defineNuxtConfig({ scripts: { registry: { stripe: { trigger: 'onNuxtReady' }, } } }) ``` ```ts [Production only] export default defineNuxtConfig({ $production: { scripts: { registry: { stripe: { trigger: 'onNuxtReady' }, } } } }) ``` :: ## ScriptStripePricingTable `ScriptStripePricingTable` embeds a [Stripe Pricing Table](https://docs.stripe.com/payments/checkout/pricing-table) and defers its script until the component is visible. An [Element Event Trigger](/docs/guides/script-triggers#element-event-triggers) delays the pricing table script until the component becomes visible. ::callout You'll need to create your own [Pricing Table](https://dashboard.stripe.com/pricing-tables) before proceeding. :: ### Demo ::code-group :::stripe-demo{label="Output"} ::: ```vue [Input] ``` :: ### Component API See the [Facade Component API](/docs/guides/facade-components#facade-components-api) for full props, events, and slots. ## [`useScriptStripe()`{lang="ts"}](/scripts/stripe){lang="ts"} Use [`useScriptStripe()`{lang="ts"}](/scripts/stripe){lang="ts"} when you need to load Stripe.js and call it programmatically. ```ts export function useScriptStripe(_options?: StripeInput) {} ``` For triggers and other script options, see [Registry Scripts](/docs/guides/registry-scripts). ::script-types :: ## Example This mounts a Payment Element for a $10.99 USD payment. You still need to finish the payment on your server; Stripe's [deferred-intent guide](https://docs.stripe.com/payments/accept-a-payment-deferred) covers the complete flow. ```vue ``` --- - **Page:** Lemon Squeezy - **Source:** https://scripts.nuxt.com/scripts/lemon-squeezy - **Description:** Load Lemon.js or turn checkout links into lazy overlay triggers. [Lemon Squeezy](https://www.lemonsqueezy.com/) is a merchant-of-record platform for digital products and subscriptions. Use [`useScriptLemonSqueezy()`{lang="ts"}](#usescriptlemonsqueezy){lang="ts"} for Lemon.js calls. [``{lang="html"}](#scriptlemonsqueezy){lang="html"} turns checkout links into overlay triggers. ::script-stats :: ::script-docs :: ## [``{lang="html"}](/scripts/lemon-squeezy){lang="html"} The headless [facade component](/docs/guides/facade-components) scans the links present when it mounts. It loads [Lemon.js](https://docs.lemonsqueezy.com/guides/developer-guide/lemonjs) when the component's root enters the viewport. ```vue ``` At mount, it adds the `.lemonsqueezy-button` class to links inside the component. Links inserted later are not scanned. Add the class yourself and call `Refresh()`{lang="ts"}, or remount the component, after adding a checkout link dynamically. ### Demo ::code-group :::lemon-squeezy-demo{label="Output"} ::: ```vue [Input] ``` :: ### Component API See the [Facade Component API](/docs/guides/facade-components#facade-components-api) for full props, events, and slots. ### Events ***`lemon-squeezy-event`*** The component forwards [Lemon.js overlay events](https://docs.lemonsqueezy.com/help/lemonjs/handling-events) through this event. The payload contains an `event` name and, for events such as `Checkout.Success`, a `data` object. ::warning The component emits the correct object at runtime, but the current `LemonSqueezyEventPayload` TypeScript definition intersects mutually exclusive event names and can reduce the payload to `never`. Until the type changes, accept `unknown` at your handler boundary and narrow it to the fields you use. :: ## [`useScriptLemonSqueezy()`{lang="ts"}](/scripts/lemon-squeezy){lang="ts"} Use [`useScriptLemonSqueezy()`{lang="ts"}](/scripts/lemon-squeezy){lang="ts"} when you need Lemon.js without the checkout-link component. ```ts export function useScriptLemonSqueezy(_options?: LemonSqueezyInput) {} ``` See [Registry Scripts](/docs/guides/registry-scripts) for trigger and loading options. If you use the composable without the component and add checkout links after Lemon.js loads, call [`Refresh()`{lang="ts"}](https://docs.lemonsqueezy.com/help/lemonjs/methods) so the SDK attaches overlay listeners to the new links: ```ts proxy.Refresh() ``` ::script-types :: ## Example Initialize Lemon.js for a payment link: ```vue ``` --- - **Page:** Intercom - **Source:** https://scripts.nuxt.com/scripts/intercom - **Description:** Load Intercom through a typed command queue or a click-triggered custom launcher. [Intercom](https://www.intercom.com/) provides an in-app messenger for customer conversations. Use [`useScriptIntercom()`{lang="ts"}](#usescriptintercom){lang="ts"} for direct API calls, or [``{lang="html"}](#scriptintercom){lang="html"} for a custom messenger launcher. ::script-stats :: ::script-docs :: ## [``{lang="html"}](/scripts/intercom){lang="html"} The headless facade holds back Intercom until its [element trigger](/docs/guides/script-triggers#element-event-triggers) fires. It listens for `click` by default. ### Demo ::code-group :::intercom-demo{label="Output"} ::: ```vue [Input] ``` :: ### Component API See the [Facade Component API](/docs/guides/facade-components#facade-components-api) for full props, events, and slots. ::warning The component's `api-base` prop currently forwards `app_base`, but Intercom expects [`api_base`](https://developers.intercom.com/installing-intercom/web/installation). Until that mapping changes, the prop cannot select the EU or Australian data host. Use the composable directly when you need a regional endpoint: ```ts useScriptIntercom({ app_id: 'YOUR_APP_ID', api_base: 'https://api-iam.eu.intercom.io', }) ``` :: #### With environment variables After you enable the registry entry, the module creates its public runtime-config fields. Set the app ID without adding a separate `runtimeConfig` block: ```ts [nuxt.config.ts] export default defineNuxtConfig({ scripts: { registry: { intercom: { trigger: 'onNuxtReady' }, } }, }) ``` ```text [.env] NUXT_PUBLIC_SCRIPTS_INTERCOM_APP_ID= ``` ### Events The [``{lang="html"}](/scripts/intercom){lang="html"} component emits `ready` after it mounts the messenger and `error` if the script fails to load. ```ts const emits = defineEmits<{ ready: [intercom: ReturnType] error: [] }>() ``` ```vue ``` ### Intercom API The component exposes its `intercom` instance, which is the return value of `useScriptIntercom()`{lang="ts"}. Call the [Intercom JavaScript API](https://developers.intercom.com/installing-intercom/web/methods) through its proxy: ```vue ``` ### Slots Use the slots to build the launcher and its loading states. **default** The default slot displays content while the facade is visible. **awaitingLoad** This slot displays content while Intercom waits for its configured trigger. ```vue ``` **loading** This slot displays content while Intercom is loading. `ScriptLoadingIndicator` supplies a visible state and status label: ```vue ``` The component declares an `error` slot, but its loading branch currently wins whenever `isReady` is false, including after a load failure. Use the emitted `error` event to render a fallback outside the component until that branch order is fixed. ## [`useScriptIntercom()`{lang="ts"}](/scripts/intercom){lang="ts"} Use [`useScriptIntercom()`{lang="ts"}](/scripts/intercom){lang="ts"} when you need the command queue without the launcher component. ```ts const { proxy } = useScriptIntercom({ app_id: 'YOUR_APP_ID' }) // examples proxy.Intercom('show') proxy.Intercom('update', { name: 'John Doe' }) ``` When an identified user signs out, call `shutdown` before another person uses the same browser. Intercom recommends this to clear the prior user's Messenger session: ```ts proxy.Intercom('shutdown') ``` See [Registry Scripts](/docs/guides/registry-scripts) for trigger and loading options. ::script-types :: ## Example Open Intercom from a button: ::code-group ```vue [IntercomButton.vue] ``` :: --- - **Page:** PayPal - **Source:** https://scripts.nuxt.com/scripts/paypal - **Description:** Load PayPal's JavaScript SDK v6 and create checkout or messaging sessions from Vue components. [PayPal](https://www.paypal.com) provides online checkout and payment APIs. Nuxt Scripts integrates the [PayPal JavaScript SDK v6](https://developer.paypal.com/sdk/js/reference) through: - `useScriptPayPal` composable, which loads `https://www.paypal.com/web-sdk/v6/core`. - `ScriptPayPalButtons` component that initializes the PayPal SDK v6 instance and exposes it via a scoped slot. - `ScriptPayPalMessages` component for creating a `paypal-messages` session. ::script-stats :: ::script-docs :: ::callout{type="warning"} The `trigger` prop on `ScriptPayPalButtons` and `ScriptPayPalMessages` currently has no effect on SDK loading or facade state. Configure `scripts.registry.paypal.trigger` when you need to defer the SDK itself. :: The composable uses PayPal's sandbox endpoint in development and the live endpoint in production unless you set `sandbox` explicitly. ## Types Install `@paypal/paypal-js` for full TypeScript support. ```bash pnpm add -D @paypal/paypal-js ``` Nuxt Scripts requires `@paypal/paypal-js` v11 or later. The v6 SDK types are available from `@paypal/paypal-js/sdk-v6`. ### Demo ::code-group :::pay-pal-demo{label="Output"} ::: ```vue [Input] ``` :: #### With environment variables Configure the client ID with an environment variable: ```ts [nuxt.config.ts] export default defineNuxtConfig({ scripts: { registry: { paypal: { trigger: 'onNuxtReady' }, } }, }) ``` The module automatically registers PayPal's public `clientId` runtime-config field, so you can omit a manual `runtimeConfig` block. ```text [.env] NUXT_PUBLIC_SCRIPTS_PAYPAL_CLIENT_ID= ``` The facade components currently default their `client-id` prop to the literal value `test`. That prop overrides runtime config, so pass the resolved ID to the component explicitly: ```vue ``` Without that prop, a production build uses PayPal's live endpoint with the `test` client ID. ### Composable ```ts export function useScriptPayPal(_options?: PayPalInput) {} ``` For triggers, proxying, and other script options, see [Registry Scripts](/docs/guides/registry-scripts). ::script-types :: --- - **Page:** Facade Components - **Source:** https://scripts.nuxt.com/docs/guides/facade-components - **Description:** Facade components are placeholder UI elements that get replaced once a third-party script loads. A facade renders lightweight placeholder UI until its third-party script and component are ready. ## What are facade components? A video embed, payment modal, or chat widget may fetch several resources during startup. Deferring that work helps the initial page, but inserting the real UI later can cause [Cumulative Layout Shift (CLS)](https://web.dev/articles/optimize-cls). A facade reserves the space and can show a loading state while the vendor code loads. ## Trade-offs Facade components can introduce user-experience trade-offs: - **Flash of mismatched content**: The placeholder may not look like the final UI. You may need to adjust the default styling to match your app's design. - **Unavailable interactivity**: The real element requires the script to load. Keep an alternative available if loading fails. - **Accessibility concerns**: Announce loading and failure states clearly. ## Available facades Script-backed facade components wrap the relevant `useScript()`{lang="ts"} composable and expose props, slots, and events for controlling their placeholders. Some components provide a minimally styled default placeholder. Check each component's registry page for its exact API. ## Usage guidance ### Provide an error fallback Tell the user what failed and offer another way to reach the content. ```vue ``` ### Provide a loading state with accessible feedback `ScriptLoadingIndicator` provides a visible loading state and an accessible status label. ```vue ``` ### Choose the triggering event Facade components have default triggers, which you can override for the surrounding UI. Prefer triggers that require explicit user interaction, such as a click. Loading on hover can cause subsequent events, such as clicks, to be lost while the component is replaced. ## Facade Components API The script-backed facade components share a similar API, although the exact props, slots, events, and event payloads vary by component. ### Props - `trigger`: For supported facades, the event that triggers the script to load. See [Element Event Triggers](/docs/guides/script-triggers#element-event-triggers) for more information. The PayPal facades currently expose this prop but do not use it for loading or facade state. Configure PayPal's registry `trigger` instead. ### Slots `ScriptYouTubePlayer`, used in the examples below, provides minimal default UI and several slots for customization. Other facade components may expose different slots. - `default`: Content to always display with the component. ```vue ``` - `loading`: The content to display only while the script is loading. ```vue ``` - `awaitingLoad`: The content to display only while the script is waiting to load. ```vue ``` - `error`: The content to display if the script fails to load. ```vue ``` The Crisp and Intercom components currently check their loading branch before their error branch. Their `error` events still fire, but their named `#error` slots cannot render until that ordering is fixed. Handle `@error` outside those two components for now. ### Events - `ready`: Emitted when the script or component is ready. The payload depends on the component. - `error`: Emitted when the script or component fails to load. The payload depends on the component. --- - **Page:** Installation - **Source:** https://scripts.nuxt.com/docs/getting-started/installation - **Description:** Install Nuxt Scripts in an existing Nuxt project. ## Quick Start Nuxt Scripts 2 requires Nuxt `^4.6.0 || ^5.0.0` and Unhead `^3.4.2`. The supported Node.js range is `^22.22.3 || ^24.15.0 || >=26.0.0`. Upgrade an existing project before installing: ```bash npx nuxi@latest upgrade --force ``` Run: ```bash npx nuxi@latest module add scripts ``` > [!TIP] > Generate an Agent Skill for this package using [skilld](https://github.com/skilld-dev/skilld): > > ```bash > npx skilld add @nuxt/scripts > ``` The [Nuxt module command](https://nuxt.com/docs/api/commands/module) installs Nuxt Scripts and adds it to the `modules` array in your Nuxt config. The DevTools panel uses the optional `@nuxt/scripts-devtools` package. Install the same version as `@nuxt/scripts`, then restart Nuxt: ```bash pnpm add -D @nuxt/scripts-devtools@2.0.0-beta.12 ``` ## Next Steps From here, choose the guide closest to what you need: 1. Build a small integration with the [Confetti Tutorial](/docs/getting-started/confetti-tutorial). 2. Learn when scripts load in [Script Triggers](/docs/guides/script-triggers). 3. Check the [Script Registry](/scripts) for a preconfigured integration. 4. Load an unlisted script with [`useScript()`{lang="ts"}](/docs/api/use-script){lang="ts"} or [Global Scripts](/docs/guides/global). 5. Configure [First-Party Mode](/docs/guides/first-party) or [Consent Management](/docs/guides/consent). --- - **Page:** Cookie Consent - **Source:** https://scripts.nuxt.com/docs/guides/consent - **Description:** Gate scripts behind user consent and drive vendor-native consent APIs through a typed per-script `consent` object. ::callout{icon="i-heroicons-play" to="https://stackblitz.com/github/nuxt/scripts/tree/main/examples/cookie-consent" target="_blank"} Try the live [Cookie Consent Example](https://stackblitz.com/github/nuxt/scripts/tree/main/examples/cookie-consent) or [Granular Consent Example](https://stackblitz.com/github/nuxt/scripts/tree/main/examples/granular-consent) on [StackBlitz](https://stackblitz.com). :: ## Choose the consent layer Consent affects two separate moments: whether the browser loads a script, and which consent state an already-loaded vendor receives. Nuxt Scripts exposes one API for each: 1. **[`useScriptTriggerConsent()`{lang="ts"}](/docs/api/use-script-trigger-consent)**: a binary load gate for the normal main-thread path. The script only starts loading after consent is granted. The experimental Partytown path is an exception because it writes its tag during SSR and ignores trigger timing. 2. **Per-script `consent` object** returned from every consent-aware `useScriptX()`{lang="ts"}: the vendor's typed API for granting, revoking, or updating consent after load. Its `defaultConsent` option sets the policy *before* the vendor's init call. Nuxt Scripts maps each integration to the vendor's vocabulary where practical. For example, Google Analytics and Google Tag Manager use [Consent Mode v2](https://developers.google.com/tag-platform/security/guides/consent), while Matomo uses [`setConsentGiven`/`forgetConsentGiven`](https://developer.matomo.org/guides/tracking-consent). The table below lists the Nuxt Scripts surface; it is not a shared cross-vendor standard. ## Binary load gate For a binary cookie banner, pass one consent trigger to every main-thread script that must wait for acceptance. Do not use the current Partytown path for a script whose tag must be held until opt-in. ::code-group ```ts [utils/cookie.ts] export const scriptsConsent = useScriptTriggerConsent() ``` ```vue [app.vue] ``` ```vue [components/cookie-banner.vue] ``` :: ### Reactive source Pass a `Ref`{lang="html"} if an external store owns the state. ```ts const agreedToCookies = ref(false) const consent = useScriptTriggerConsent({ consent: agreedToCookies }) ``` ### Revoking Consent revocation flips the reactive `consented` ref. Once the load-gate promise resolves, the script has loaded; watch `consented` if you need to tear it down after revocation. ```vue ``` ### Delaying the load after consent ```ts const consent = useScriptTriggerConsent({ consent: agreedToCookies, postConsentTrigger: () => new Promise(resolve => setTimeout(resolve, 3000), ), }) ``` Revoking during that three-second delay changes `consented` to `false`, but it does not cancel the pending timer; the trigger still resolves when the timer finishes. ## Per-script consent API Every consent-aware `useScriptX()`{lang="ts"} returns a `consent` object typed to the vendor's API. `defaultConsent` sets the initial policy in `clientInit`, before the vendor's first call. Your cookie banner can then call `consent.*` to update it. Google Analytics and Google Tag Manager also expose the raw runtime command `consent.default(state)`{lang="ts"}. Use the `defaultConsent` option for the initial state: `consent.default()`{lang="ts"} runs after the composable's `clientInit`. At that point, Analytics has queued its `js` and `config` commands, while Tag Manager has queued its `gtm.start` / `gtm.js` event. Both Google consent methods validate against the Consent Mode v2 schema and warn through `consola` about unknown keys or values other than `granted` and `denied`. ```ts const { consent } = useScriptGoogleAnalytics({ id: 'G-XXXXXXXX', defaultConsent: { ad_storage: 'denied', analytics_storage: 'denied' }, }) function onAcceptAll() { consent.update({ ad_storage: 'granted', ad_user_data: 'granted', ad_personalization: 'granted', analytics_storage: 'granted', }) } ``` ### Per-vendor surface | Script | `defaultConsent` | Runtime `consent.*` | | ------------------ | ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | | Google Analytics | `ConsentState \| ConsentState[]`{lang="html"} (GCMv2) | `consent.default(state)`{lang="ts"} / `consent.update(state)`{lang="ts"} | | Google Tag Manager | `ConsentState \| ConsentState[]`{lang="html"} (GCMv2) | `consent.default(state)`{lang="ts"} / `consent.update(state)`{lang="ts"} | | Bing UET | `{ ad_storage }` | `consent.update({ ad_storage })`{lang="ts"} | | Meta Pixel | `'granted' \| 'denied'` | `consent.grant()`{lang="ts"} / `consent.revoke()`{lang="ts"} | | TikTok Pixel | `'granted' \| 'denied' \| 'hold'` | `consent.grant()`{lang="ts"} / `consent.revoke()`{lang="ts"} / `consent.hold()`{lang="ts"} | | Matomo | `'required' \| 'given' \| 'not-required'` | `consent.give()`{lang="ts"} / `consent.forget()`{lang="ts"} *(requires `defaultConsent: 'required'` or `'given'`)* | | Mixpanel | `'opt-in' \| 'opt-out'` | `consent.optIn()`{lang="ts"} / `consent.optOut()`{lang="ts"} | | PostHog | `'opt-in' \| 'opt-out'` | `consent.optIn()`{lang="ts"} / `consent.optOut()`{lang="ts"} | | Clarity | `boolean` *(the current schema also admits an undocumented record value)* | `consent.set(value)`{lang="ts"} | See each script's registry page for vendor-specific caveats. ### Fanning out to multiple scripts Vendors do not share a normalized consent model. When one banner controls several scripts, call each consent API explicitly: ```ts const ga = useScriptGoogleAnalytics({ id: 'G-XXX', defaultConsent: { ad_storage: 'denied', analytics_storage: 'denied' } }) const meta = useScriptMetaPixel({ id: '123', defaultConsent: 'denied' }) const matomo = useScriptMatomoAnalytics({ cloudId: 'foo.matomo.cloud', defaultConsent: 'required' }) function onAcceptAll() { ga.consent.update({ ad_storage: 'granted', ad_user_data: 'granted', ad_personalization: 'granted', analytics_storage: 'granted', }) meta.consent.grant() matomo.consent.give() } function onDeclineAll() { ga.consent.update({ ad_storage: 'denied', ad_user_data: 'denied', ad_personalization: 'denied', analytics_storage: 'denied', }) meta.consent.revoke() matomo.consent.forget() } ``` ### Granular categories For separate analytics and marketing choices, map each choice only to the categories that vendor understands: ```ts function savePreferences(choices: { analytics: boolean, marketing: boolean }) { ga.consent.update({ analytics_storage: choices.analytics ? 'granted' : 'denied', ad_storage: choices.marketing ? 'granted' : 'denied', ad_user_data: choices.marketing ? 'granted' : 'denied', ad_personalization: choices.marketing ? 'granted' : 'denied', }) if (choices.marketing) meta.consent.grant() else meta.consent.revoke() if (choices.analytics) matomo.consent.give() else matomo.consent.forget() } ``` ## Third-party CMP recipes If a Consent Management Platform owns the UI, bridge its events into each script's `consent` API. ### [OneTrust](https://developer.onetrust.com/onetrust/docs/single-page-applications) ```ts const ga = useScriptGoogleAnalytics({ id: 'G-XXX', defaultConsent: { ad_storage: 'denied', analytics_storage: 'denied' } }) const meta = useScriptMetaPixel({ id: '123', defaultConsent: 'denied' }) onNuxtReady(() => { function apply() { const groups = (window as any).OnetrustActiveGroups as string | undefined if (!groups) return const activeGroups = new Set(groups.split(',').filter(Boolean)) const analytics = activeGroups.has('C0002') const marketing = activeGroups.has('C0004') ga.consent.update({ analytics_storage: analytics ? 'granted' : 'denied', ad_storage: marketing ? 'granted' : 'denied', ad_user_data: marketing ? 'granted' : 'denied', ad_personalization: marketing ? 'granted' : 'denied', }) if (marketing) meta.consent.grant() else meta.consent.revoke() } apply() window.addEventListener('OneTrustGroupsUpdated', apply) }) ``` ### [Cookiebot](https://www.cookiebot.com/en/developer/) ```ts const ga = useScriptGoogleAnalytics({ id: 'G-XXX', defaultConsent: { ad_storage: 'denied', analytics_storage: 'denied' } }) const meta = useScriptMetaPixel({ id: '123', defaultConsent: 'denied' }) onNuxtReady(() => { function apply() { const cb = (window as any).Cookiebot if (!cb?.consent) return ga.consent.update({ analytics_storage: cb.consent.statistics ? 'granted' : 'denied', ad_storage: cb.consent.marketing ? 'granted' : 'denied', ad_user_data: cb.consent.marketing ? 'granted' : 'denied', ad_personalization: cb.consent.marketing ? 'granted' : 'denied', }) if (cb.consent.marketing) meta.consent.grant() else meta.consent.revoke() } apply() window.addEventListener('CookiebotOnConsentReady', apply) window.addEventListener('CookiebotOnAccept', apply) window.addEventListener('CookiebotOnDecline', apply) }) ``` --- - **Page:** YouTube Player - **Source:** https://scripts.nuxt.com/scripts/youtube-player - **Description:** Show performance-optimized YouTube videos in your Nuxt app. [YouTube](https://youtube.com/) hosts videos and provides an iframe player API. Nuxt Scripts provides a [`useScriptYouTubePlayer()`{lang="ts"}](/scripts/youtube-player){lang="ts"} composable and a headless [``{lang="html"}](/scripts/youtube-player){lang="html"} component for controlling the YouTube player. ::script-stats :: ::script-docs :: ## Types Install `@types/youtube` for full TypeScript support. ```bash pnpm add -D @types/youtube ``` ## [``{lang="html"}](/scripts/youtube-player){lang="html"} [``{lang="html"}](/scripts/youtube-player){lang="html"} wraps [`useScriptYouTubePlayer()`{lang="ts"}](/scripts/youtube-player){lang="ts"} with a lazy thumbnail and a headless player UI. An [Element Event Trigger](/docs/guides/script-triggers#element-event-triggers) delays the iframe API and player until the configured event fires. The default event is `mousedown`. ### Demo ::code-group :::youtube-demo{label="Output"} ::: ```vue [Input] ``` :: ### Privacy The ``{lang="html"} component uses YouTube's privacy-enhanced `https://www.youtube-nocookie.com` host by default. See YouTube's [embed instructions](https://support.google.com/youtube/answer/171780) for details. To use the standard cookie-enabled host, set the `cookies` prop. ```vue ``` ### Placeholder The YouTube Player placeholder is a 1280x720 WebP image that is lazy-loaded by default. Set `thumbnailSize` to change the placeholder size. Set `webp` to `false` for a JPEG thumbnail. ```vue ``` For finer control, set `placeholderAttrs` or replace the image through the `#placeholder` slot. #### Eager Loading For an above-the-fold video, load the thumbnail eagerly or replace it through the `#placeholder` slot. ::code-group ```vue [Placeholder Attrs] ``` ```vue [Placeholder Slot] ``` :: ### Component API See the [Facade Component API](/docs/guides/facade-components#facade-components-api) for full props, events, and slots. ### Events The component forwards the six events below. At runtime, each handler receives only the event object shown here. A failure while loading the iframe API also emits `error` with no arguments. The component's current TypeScript declaration lists a second `YT.Player` argument for five events, but that argument is not emitted. The YouTube API defines `onAutoplayBlocked` too, but the component does not currently forward it. See [Player Events](https://developers.google.com/youtube/iframe_api_reference#Events) for payload details. ```ts const emits = defineEmits<{ 'ready': [e: YT.PlayerEvent] 'state-change': [e: YT.OnStateChangeEvent] 'playback-quality-change': [e: YT.OnPlaybackQualityChangeEvent] 'playback-rate-change': [e: YT.OnPlaybackRateChangeEvent] 'error': [e: YT.OnErrorEvent] 'api-change': [e: YT.PlayerEvent] }>() ``` ### Slots Use the slots to control the facade around the player. **default** Always visible. ```vue ``` **awaitingLoad** Shown while the component waits for its element trigger. ```vue ``` **loading** Shown while the iframe API loads. ```vue ``` **placeholder** Replaces the default YouTube thumbnail. The slot receives the computed `placeholder` URL. ```vue ``` ## [`useScriptYouTubePlayer()`{lang="ts"}](/scripts/youtube-player){lang="ts"} Use [`useScriptYouTubePlayer()`{lang="ts"}](/scripts/youtube-player){lang="ts"} when you need to load the iframe API and create a player programmatically. ```ts export function useScriptYouTubePlayer(_options: YouTubePlayerInput) {} ``` For triggers, proxying, and other script options, see [Registry Scripts](/docs/guides/registry-scripts). ::script-types :: ## Example Loading the YouTube Player SDK and interacting with it programmatically. ```vue ``` --- - **Page:** Vimeo Player - **Source:** https://scripts.nuxt.com/scripts/vimeo-player - **Description:** Show performance-optimized Vimeo videos in your Nuxt app. [Vimeo](https://vimeo.com/) hosts videos and provides a JavaScript player API. Nuxt Scripts provides a [`useScriptVimeoPlayer()`{lang="ts"}](/scripts/vimeo-player){lang="ts"} composable and a headless [``{lang="html"}](/scripts/vimeo-player){lang="html"} component for interacting with the Vimeo Player. ::script-stats :: ::script-docs :: ## Types Install `@vimeo/player`, which includes its own types, for full TypeScript support. ```bash pnpm add -D @vimeo/player ``` ## [``{lang="html"}](/scripts/vimeo-player){lang="html"} [``{lang="html"}](/scripts/vimeo-player){lang="html"} wraps [`useScriptVimeoPlayer()`{lang="ts"}](/scripts/vimeo-player){lang="ts"} with a lazy placeholder and headless player UI. An [Element Event Trigger](/docs/guides/script-triggers#element-event-triggers) delays the Vimeo player until the configured event fires. The default event is `mousedown`. ::callout{color="amber"} The player accepts either `id` or `url`, but the current oEmbed thumbnail request reads only `id`. When you pass only `url`, provide your own `#placeholder` content or also pass the numeric video ID. :: ### Demo ::code-group :::vimeo-demo{label="Output"} ::: ```vue [Input] ``` :: #### Eager Loading Placeholder The Vimeo video placeholder is lazy-loaded by default. For an above-the-fold video, load the image eagerly or replace it through the `#placeholder` slot. ::code-group ```vue [Placeholder Attrs] ``` ```vue [Placeholder Slot] ``` :: ### Component API See the [Facade Component API](/docs/guides/facade-components#facade-components-api) for full props, events, and slots. ### Events The component forwards the Vimeo Player SDK events below. See [Player Events](https://developer.vimeo.com/player/sdk/reference#about-player-events) for payload details. A failure while loading the SDK also emits `error`, but without the event and player arguments declared for Vimeo's own `error` event. ```ts const emits = defineEmits<{ play: [e: EventMap['play'], player: Player] playing: [e: EventMap['playing'], player: Player] pause: [e: EventMap['pause'], player: Player] ended: [e: EventMap['ended'], player: Player] timeupdate: [e: EventMap['timeupdate'], player: Player] progress: [e: EventMap['progress'], player: Player] seeking: [e: EventMap['seeking'], player: Player] seeked: [e: EventMap['seeked'], player: Player] texttrackchange: [e: EventMap['texttrackchange'], player: Player] chapterchange: [e: EventMap['chapterchange'], player: Player] cuechange: [e: EventMap['cuechange'], player: Player] cuepoint: [e: EventMap['cuepoint'], player: Player] volumechange: [e: EventMap['volumechange'], player: Player] playbackratechange: [e: EventMap['playbackratechange'], player: Player] bufferstart: [e: EventMap['bufferstart'], player: Player] bufferend: [e: EventMap['bufferend'], player: Player] error: [e: EventMap['error'], player: Player] loaded: [e: EventMap['loaded'], player: Player] durationchange: [e: EventMap['durationchange'], player: Player] fullscreenchange: [e: EventMap['fullscreenchange'], player: Player] qualitychange: [e: EventMap['qualitychange'], player: Player] camerachange: [e: EventMap['camerachange'], player: Player] resize: [e: EventMap['resize'], player: Player] enterpictureinpicture: [e: EventMap['enterpictureinpicture'], player: Player] leavepictureinpicture: [e: EventMap['leavepictureinpicture'], player: Player] }>() ``` ### Slots Use the slots to control the facade around the player. **default** Always visible. ```vue ``` **awaitingLoad** Shown while the component waits for its element trigger. ```vue ``` **loading** Shown while the Vimeo SDK loads. ```vue ``` **placeholder** Replaces the default Vimeo thumbnail. The slot receives the resolved `placeholder` URL. ```vue ``` ## [`useScriptVimeoPlayer()`{lang="ts"}](/scripts/vimeo-player){lang="ts"} Use [`useScriptVimeoPlayer()`{lang="ts"}](/scripts/vimeo-player){lang="ts"} when you need to load the Vimeo Player SDK and create a player programmatically. ```ts export function useScriptVimeoPlayer(_options?: VimeoPlayerInput) {} ``` For triggers, proxying, and other script options, see [Registry Scripts](/docs/guides/registry-scripts). ::script-types :: ## Example Loading the Vimeo Player SDK and interacting with it programmatically. ```vue ``` --- - **Page:** Script Registry ยท Nuxt Scripts - **Source:** https://scripts.nuxt.com/scripts - **Description:** The registry is a collection of third-party scripts with out-of-the-box composable and component integrations for Nuxt Scripts. Nuxt Scripts home Nuxt Scripts on GitHub **49 scripts ** # **Script Registry ** Third-party scripts with composable and component integrations. [Learn more โ†’](https://scripts.nuxt.com/docs/guides/registry-scripts) [Install Nuxt Scripts](https://scripts.nuxt.com/docs/getting-started/installation) [How integrations work](https://scripts.nuxt.com/docs/guides/registry-scripts) ## [Analytics](https://scripts.nuxt.com/scripts/categories/analytics) 17 **Fathom Analytics** **Recommended ****$10 credit** [Card link](https://scripts.nuxt.com/scripts/fathom-analytics) **Rybbit Analytics** **Recommended ** [Card link](https://scripts.nuxt.com/scripts/rybbit-analytics) **Ahrefs Web Analytics** [Card link](https://scripts.nuxt.com/scripts/ahrefs-analytics) **Clarity** [Card link](https://scripts.nuxt.com/scripts/clarity) **Cloudflare Web Analytics** [Card link](https://scripts.nuxt.com/scripts/cloudflare-web-analytics) **Databuddy Analytics** [Card link](https://scripts.nuxt.com/scripts/databuddy-analytics) **Google Analytics** [Card link](https://scripts.nuxt.com/scripts/google-analytics) **Hotjar** [Card link](https://scripts.nuxt.com/scripts/hotjar) **Matomo Analytics** [Card link](https://scripts.nuxt.com/scripts/matomo-analytics) **Mixpanel** [Card link](https://scripts.nuxt.com/scripts/mixpanel-analytics) **Plausible Analytics** [Card link](https://scripts.nuxt.com/scripts/plausible-analytics) **PostHog** [Card link](https://scripts.nuxt.com/scripts/posthog) **Pulse Analytics** [Card link](https://scripts.nuxt.com/scripts/pulse-analytics) **Segment** [Card link](https://scripts.nuxt.com/scripts/segment) **SpeedCurve LUX** [Card link](https://scripts.nuxt.com/scripts/speedcurve) **Umami Analytics** [Card link](https://scripts.nuxt.com/scripts/umami-analytics) **Vercel Analytics** [Card link](https://scripts.nuxt.com/scripts/vercel-analytics) ## [Advertising](https://scripts.nuxt.com/scripts/categories/advertising) 9 **Bing UET** [Card link](https://scripts.nuxt.com/scripts/bing-uet) **Carbon Ads** [Card link](https://scripts.nuxt.com/scripts/carbon-ads) **Google AdSense** [Card link](https://scripts.nuxt.com/scripts/google-adsense) **LinkedIn Insight Tag** [Card link](https://scripts.nuxt.com/scripts/linkedin-insight) **Meta Pixel** [Card link](https://scripts.nuxt.com/scripts/meta-pixel) **Reddit Pixel** [Card link](https://scripts.nuxt.com/scripts/reddit-pixel) **Snapchat Pixel** [Card link](https://scripts.nuxt.com/scripts/snapchat-pixel) **TikTok Pixel** [Card link](https://scripts.nuxt.com/scripts/tiktok-pixel) **X Pixel** [Card link](https://scripts.nuxt.com/scripts/x-pixel) ## [Content](https://scripts.nuxt.com/scripts/categories/content) 6 **Bluesky Embed** [Card link](https://scripts.nuxt.com/scripts/bluesky-embed) **Google Maps** [Card link](https://scripts.nuxt.com/scripts/google-maps) **Instagram Embed** [Card link](https://scripts.nuxt.com/scripts/instagram-embed) **Leaflet** [Card link](https://scripts.nuxt.com/scripts/leaflet) **MapLibre GL JS** [Card link](https://scripts.nuxt.com/scripts/maplibre) **X Embed** [Card link](https://scripts.nuxt.com/scripts/x-embed) ## [Tools](https://scripts.nuxt.com/scripts/categories/tools) 6 **Calendly** [Card link](https://scripts.nuxt.com/scripts/calendly) **Google reCAPTCHA** [Card link](https://scripts.nuxt.com/scripts/google-recaptcha) **Google Sign-In** [Card link](https://scripts.nuxt.com/scripts/google-sign-in) **Gravatar** [Card link](https://scripts.nuxt.com/scripts/gravatar) **Statable Analytics** [Card link](https://scripts.nuxt.com/scripts/statable-analytics) **Usercentrics** [Card link](https://scripts.nuxt.com/scripts/usercentrics) ## [Support](https://scripts.nuxt.com/scripts/categories/support) 4 **Crisp** [Card link](https://scripts.nuxt.com/scripts/crisp) **DeskCrew** [Card link](https://scripts.nuxt.com/scripts/deskcrew) **Intercom** [Card link](https://scripts.nuxt.com/scripts/intercom) **Tawk.to** [Card link](https://scripts.nuxt.com/scripts/tawk-to) ## [Tag Manager](https://scripts.nuxt.com/scripts/categories/tag-manager) 1 **Google Tag Manager** [Card link](https://scripts.nuxt.com/scripts/google-tag-manager) ## [Payments](https://scripts.nuxt.com/scripts/categories/payments) 3 **Lemon Squeezy** [Card link](https://scripts.nuxt.com/scripts/lemon-squeezy) **PayPal** [Card link](https://scripts.nuxt.com/scripts/paypal) **Stripe** [Card link](https://scripts.nuxt.com/scripts/stripe) ## [CDN](https://scripts.nuxt.com/scripts/categories/cdn) 1 **NPM** [Card link](https://scripts.nuxt.com/scripts/npm) ## [Video](https://scripts.nuxt.com/scripts/categories/video) 2 **Vimeo Player** [Card link](https://scripts.nuxt.com/scripts/vimeo-player) **YouTube Player** [Card link](https://scripts.nuxt.com/scripts/youtube-player) **Missing an integration? ** Suggest a new script for the registry. [Suggest a Script](https://github.com/nuxt/scripts/discussions/177) --- - **Page:** Learn ยท Nuxt Scripts - **Source:** https://scripts.nuxt.com/learn - **Description:** Deep dives into third-party script performance, privacy, and best practices for Nuxt. Nuxt Scripts home Nuxt Scripts on GitHub # **Learn** Deep dives into third-party script performance, privacy, and best practices for Nuxt. [

The True Cost of Analytics Scripts: Performance Benchmarks for 2026

We benchmarked 10 analytics providers by transfer size, decoded size, execution time, and memory impact. The results show Google Analytics costs 156x more decoded bytes than Plausible - and why that matters for INP.March 18, 20268 min read](https://scripts.nuxt.com/learn/analytics-script-performance) [

Choosing Analytics for Your Nuxt App: A Data-Backed Guide

Stop choosing analytics by brand. We measured transfer size, execution cost, and privacy profiles of every major provider to help you pick the right one for your Nuxt app.March 18, 20268 min read](https://scripts.nuxt.com/learn/choosing-analytics-nuxt) [

Best Privacy-First Analytics Compared: Plausible vs Fathom vs Umami + 4 More

We ran AST analysis on 7 privacy-first analytics tools and catalogued exactly which browser APIs each one touches. See how Plausible, Fathom, Umami, Cloudflare, Rybbit, Databuddy, and Vercel Analytics compare on privacy, performance, and GDPR compliance.March 18, 20268 min read](https://scripts.nuxt.com/learn/privacy-first-analytics-compared) --- - **Page:** The True Cost of Analytics Scripts: Performance Benchmarks for 2026 - **Source:** https://scripts.nuxt.com/learn/analytics-script-performance - **Description:** We benchmarked 10 analytics providers by transfer size, decoded size, execution time, and memory impact. The results show Google Analytics costs 156x more decoded bytes than Plausible - and why that matters for INP. Most performance audits focus on your own code: image optimization, component lazy-loading, bundle splitting. The analytics script gets a free pass because it's "just tracking." It shouldn't. We measured 10 providers across three metrics that determine real-world impact on your users. The spread is an order of magnitude. ## The 2026 Analytics Performance Matrix Six metrics tell the full story: **Transfer Size** (bytes over the wire), **Decoded Size** (what the browser parses and executes), **Script Duration** (pure V8 execution time), **Task Duration** (total main-thread blocking), **Heap Impact** (memory allocated), and **Request Count** (network overhead). ::benchmark-table :: ::chart-analytics-sizes :: Rybbit's 1,135ms load time and GA4's 585ms stand out immediately-but the decoded size column is where the real damage happens. ## Why Decoded Size is the New Bottleneck Developers fixate on gzipped transfer size. The browser doesn't execute gzip-it executes the decoded JavaScript. Those numbers are different. V8 can parse and compile eagerly selected code [on a background thread](https://v8.dev/blog/explicit-compile-hints), but **JavaScript execution still competes for main-thread time.** Every byte of decoded JS that contains function calls, closures, and event listeners can delay the same thread that handles user interactions. The gap between providers is stark. GA4 at 452.9 KB decoded is **156x heavier** than Plausible at 2.9 KB, and our benchmarks show it: GA4 burns 20ms of pure script execution and 33ms of total task time, vs 1ms script / 6ms task for Plausible. On mid-range mobile hardware, 400 KB of decoded JavaScript translates to roughly **150-250ms of main-thread contention** ([Addy Osmani, "Cost of JavaScript" 2024](https://medium.com/p/the-cost-of-javascript-in-2023-7eb669046f2)). GA4 also allocates +2.6 MB of heap memory compared to Plausible's +698 KB. That contention directly degrades [Interaction to Next Paint (INP)](https://web.dev/articles/inp)-Chrome's primary responsiveness metric since March 2024. If your analytics script fires during the first few seconds of a visit, you're burning INP budget on tracking code rather than user interactions. ### Core Web Vitals Impact The benchmark table above includes measured CWV estimates for every script: LCP impact (how much script execution delays rendering), INP risk (based on script weight and DOM observation), and CLS elements (injected visible elements that shift layout). The heaviest scripts show consistent LCP delays. On a local page with no network variance, scripts like GA4 and Matomo add measurable rendering delays from pure execution time. Field numbers on real mobile connections will be worse - a script that costs 5-20ms of LCP on desktop can easily cost 30-50ms on mid-range mobile, which is the difference between passing and failing the 2.5s LCP threshold. Scripts that combine heavy execution with `MutationObserver` usage (like GA4) carry a "high" INP risk - they not only block during initialization but continue to compete for the main thread on every DOM change. Here's the full audit for GA4 - note the privacy footprint, network activity, and cookie surface: ::script-stats{scriptKey="googleAnalytics"} :: ::chart-analytics-load-time :: ::note Every 100 KB of gzipped JS that decodes to \~400 KB adds roughly [100-150ms of main-thread work](https://devclass.com/2024/05/15/google-chrome-engineers-propose-explicit-compile-hints-to-speed-up-javascript-heavy-sites/) on average mobile hardware. GA4 at 154.3 KB compressed sits right at that threshold. :: ## The 2025 Fingerprinting Pivot In February 2025, Google officially reversed its stance on digital fingerprinting, permitting canvas and WebGL fingerprinting in Google Ads provided advertisers disclose it in privacy policies. The UK's ICO called it ["irresponsible"](https://www.forbes.com/sites/zakdoffman/2024/12/19/google-chrome-privacy-u-turn-as-ico-slams-irresponsible-fingerprinting-change/). GA4 is currently the only provider in our benchmark set using these techniques. The privacy implications have extensive coverage elsewhere. The performance cost gets less attention: canvas fingerprinting requires **synchronous GPU readbacks**, which add [10-30ms of latency](https://coveryourtracks.eff.org/) directly to the main thread during script evaluation ([Brave Privacy Blog](https://brave.com/privacy-updates/7-fingerprinting-protections/)). It's synchronous and blocks the main thread. For a script already carrying 585ms of load time, an additional 10-30ms of synchronous GPU work is a rounding error in isolation. Combined with everything else GA4 does on initialization, it's one more reason the script consistently underperforms in field data. ## Compression Wars: zstd vs the World The compression story changed in 2025. Zstandard (`zstd`) reached baseline support across all major browsers with iOS 18 and macOS Sequoia joining Chrome 123+ and Firefox 126+. That means you can serve `zstd`-compressed assets to your entire user base. The numbers: `zstd` decompresses at [600-800+ MB/s, roughly **3x faster** than Brotli or Gzip](https://blog.cloudflare.com/zstd-compression-rules/) (Cloudflare, 2024). Lower CPU overhead on mobile means faster decoding with less battery drain. :LinkScript{slug="cloudflareWebAnalytics"} and :LinkScript{slug="vercelAnalytics"} have already shifted their analytics delivery to `zstd`. This is part of why Cloudflare Web Analytics (10.8 KB transfer, 30.4 KB decoded, 86ms load) punches above its weight in load time despite having a relatively large decoded footprint. The decompression cost is lower, and they've optimized the delivery pipeline. If your self-hosted Matomo or custom analytics endpoint is still serving Brotli or Gzip, switching to `zstd` is a free win. ## How Nuxt Scripts Loads Analytics The standard analytics integration pattern-copy a snippet, paste it into a script tag-has predictable performance problems. **Dynamic injection hides from the preload scanner.** Scripts created with `document.createElement('script')`{lang="js"} are invisible to Chrome's preload scanner, which runs ahead of the parser to discover resources early. Dynamic injection means the browser can't queue the network request until the script that creates the element executes. That delay is typically [**200-500ms**](https://www.debugbear.com/blog/script-async-vs-defer) ([DebugBear, 2024](https://www.debugbear.com/blog/script-async-vs-defer)). Nuxt Scripts uses declarative loading, emitting the script as a standard script tag that the preload scanner can discover immediately-without blocking the initial render. **Initialization timing matters as much as load timing.** Even a fast-loading script can hurt performance if it executes during the critical rendering window. Nuxt Scripts initializes analytics via `requestIdleCallback` by default, which defers execution until the browser's main thread has finished painting and handling any pending user input. ```ts [nuxt.config.ts] export default defineNuxtConfig({ scripts: { registry: { plausibleAnalytics: { domain: 'your-domain.com' } } } }) ``` That single config line handles declarative injection, idle-time initialization, and SPA navigation tracking automatically. The manual alternative requires a plugin, `process.client` guards, a router watcher, and still probably gets the timing wrong. ::tip Nuxt Scripts also supports first-party proxying, routing analytics requests through your own domain to bypass ad blockers. At [25-45% blocker adoption](https://blockthrough.com) in many audiences ([Blockthrough Ad-Block Report 2026](https://blockthrough.com)), this restores significant data accuracy. :: ## The 19-Second Death March In emerging markets, average load time over 3G sits around [**19 seconds**](https://hpbn.co/) ([Google/HPBN](https://hpbn.co/)). For users on that connection, a 154.3 KB GA4 script becomes a failure point. Unstable mobile connections drop requests. Expensive data plans mean users disable images and scripts. A script that takes 585ms to become interactive on a fast connection might simply never load. A [0.1-second improvement in mobile load time increases retail conversion by **8.4%**](https://www.siteqwality.com/post/page-speed-vs-conversion-rate) (Google/Deloitte, 2024). Swapping GA4 for Plausible-1.9 KB vs 154.3 KB on the wire-can recover [10-15 PageSpeed points and reduce Total Blocking Time by 100-200ms on mobile](https://www.debugbear.com/blog/third-party-scripts). Sub-5 KB providers (:LinkScript, :LinkScript{slug="plausibleAnalytics"}, :LinkScript{slug="umamiAnalytics"}, :LinkScript{slug="fathomAnalytics"}) make sites accessible to users who would otherwise abandon them before the first interaction. For those users, this determines whether your site loads at all. ## Making the Call Compare that GA4 audit to the lightweight alternative: ::script-stats{scriptKey="plausibleAnalytics"} :: The benchmark data makes the performance winner clear: :LinkScript{slug="vercelAnalytics"}, :LinkScript{slug="plausibleAnalytics"}, and :LinkScript{slug="umamiAnalytics"} are in a different category from everyone else. If you don't need :LinkScript{slug="segment"}'s data routing, :LinkScript{slug="googleAnalytics"}'s Google Ads integration, or :LinkScript{slug="matomoAnalytics"}'s on-premise compliance features, there's no performance justification for using them. If you do need those features, Nuxt Scripts' idle-time initialization and declarative loading reduce the damage. You can't make 452.9 KB of decoded JavaScript free, but you can stop it from running at the worst possible moment. For the full breakdown of which provider fits which use case, see [Choosing the Right Analytics for Nuxt](/learn/choosing-analytics-nuxt). For privacy-specific comparisons including cookie policies and GDPR compliance, see [Privacy-First Analytics Compared](/learn/privacy-first-analytics-compared). --- - **Page:** Google Maps - **Source:** https://scripts.nuxt.com/scripts/google-maps - **Description:** Load interactive maps, static maps, and location search on demand. [Google Maps](https://maps.google.com/) provides interactive and static maps for websites. Nuxt Scripts provides a [`useScriptGoogleMaps()`{lang="ts"}](/scripts/google-maps/api/use-script-google-maps){lang="ts"} composable and a headless [``{lang="html"}](/scripts/google-maps/api/script-google-maps){lang="html"} component for working with Google Maps. ::script-types{exclude-components} :: ## Types Install `@types/google.maps` for full TypeScript support. ```bash pnpm add -D @types/google.maps ``` ## Setup Enable Google Maps in your `nuxt.config` and provide your API key via environment variable: ```ts [nuxt.config.ts] export default defineNuxtConfig({ scripts: { registry: { // Register infrastructure without loading Maps on every page. // will load it after its element trigger fires. googleMaps: {}, }, }, }) ``` ```text [.env] NUXT_PUBLIC_SCRIPTS_GOOGLE_MAPS_API_KEY= ``` Add `trigger: 'onNuxtReady'` to the registry entry only when you want the interactive Maps API to load globally. It bypasses the component's default interaction delay because the shared script instance is already loading. ::callout{color="amber"} The Maps JavaScript and Static Maps APIs send this key to the browser. Follow Google's [API security guidance](https://developers.google.com/maps/api-security-best-practices): apply a Websites application restriction, allow only the APIs this site uses, and configure quota limits. Runtime config keeps deployment values configurable; it does not make a `NUXT_PUBLIC_` key secret. :: See [Billing & Permissions](/scripts/google-maps/guides/billing) for API costs and required permissions. ## Quick Start ```vue ``` See the [Markers & Info Windows](/scripts/google-maps/guides/markers-and-info-windows) guide for adding markers, popups, and custom content. See [Shapes & Overlays](/scripts/google-maps/guides/shapes-and-overlays) for drawing on the map. --- - **Page:** Tutorial: Load js-confetti - **Source:** https://scripts.nuxt.com/docs/getting-started/confetti-tutorial - **Description:** Learn how to load the js-confetti script using the Nuxt Scripts module. This tutorial loads [js-confetti](https://github.com/loonywizard/js-confetti) from [npm](https://npmjs.com). You'll call it through a proxied function and add types for its browser API. ## Background on [`useScriptNpm()`{lang="ts"}](/scripts/npm){lang="ts"} [`useScriptNpm()`{lang="ts"}](/scripts/npm){lang="ts"} is a [registry script](/scripts) built on [`useScript()`{lang="ts"}](/docs/api/use-script){lang="ts"}. It loads browser-ready package files published to [npm](https://www.npmjs.com/). Most npm packages belong in `package.json`. Loading one on demand may instead require a dynamic import, a separate chunk, and sometimes build-time transpilation. `useScriptNpm()`{lang="ts"} is useful for an occasional, non-critical browser script that already exposes a global API. Install packages your application uses throughout the codebase as normal dependencies. The three snippets below load the same file at different abstraction levels. ::code-group ```ts [Registry Script useScriptNpm] useScriptNpm({ packageName: 'js-confetti', file: 'dist/js-confetti.browser.js', version: '0.12.0', }) ``` ```ts [useScript] useScript('https://unpkg.com/js-confetti@0.12.0/dist/js-confetti.browser.js') ``` ```ts [useHead] useHead({ script: [ { src: 'https://unpkg.com/js-confetti@0.12.0/dist/js-confetti.browser.js' } ] }) ``` :: ### Loading the script Call [`useScriptNpm()`{lang="ts"}](/scripts/npm){lang="ts"} inside a component: ```vue [app.vue] ``` The browser's Network panel should now show the script request. ### Resolving the third-party script API Tell Nuxt Scripts how to resolve the script's client-side API with the [`use`](/docs/api/use-script#nuxtusescriptoptions) function: ```vue [app.vue] ``` ### Using the third-party script API The `js-confetti` library exposes a `JSConfetti` class. Create an instance after the script loads, then reuse that instance for subsequent calls. You can wait for the script explicitly or use a [proxied function](/docs/guides/key-concepts#understanding-proxied-functions) to queue a call until it is ready. ::code-group ```vue [Explicit Load] ``` ```vue [Proxy Functions] ``` :: `addConfetti` is still untyped, so the editor cannot check its arguments or offer completion. ### Adding types Pass a generic to [`useScriptNpm()`{lang="ts"}](/scripts/npm){lang="ts"} and augment `Window` with the same API: ```vue [app.vue] ``` ### Delay script loading Use `trigger` when the script should wait for application state, an event, or a timer. See the [Script Triggers](/docs/guides/script-triggers) guide for all available options. #### Using a ref A `ref` loads the script when its value becomes truthy. ```vue [app.vue] ``` ::tip You can also use a computed ref or getter function: `trigger: computed(() => someCondition.value)`{lang="ts"} or `trigger: () => shouldLoad.value`. :: #### Using element events Use [`useScriptTriggerElement()`{lang="ts"}](/docs/api/use-script-trigger-element){lang="ts"} to wait for an element interaction. ```vue [app.vue] ``` ### Bundle the script locally Nuxt Scripts bundles statically analyzable `useScriptNpm()`{lang="ts"} files by default and serves them from `/_scripts/assets/`. This avoids the initial connection to the package CDN. Set `bundle: false` if you want to load the file directly from the configured CDN instead. ```vue [app.vue] ``` Without this opt-out, the Network panel shows the script loading from your application's server. See [Key Concepts](/docs/guides/key-concepts) for more on script instances and proxied functions. --- - **Page:** Registry Scripts - **Source:** https://scripts.nuxt.com/docs/guides/registry-scripts - **Description:** Configure, reuse, and extend typed integrations from the Nuxt Scripts registry. Registry scripts are preconfigured integrations for common third-party services. Browse the available integrations in the [Script Registry](/scripts). ## Features ### Safe initialization Registry composables initialize the required global state before they add the external script. ### Loading controls Each registry entry declares the bundling, proxying, and Partytown capabilities it supports. Composable-driven integrations also accept a loading trigger. ### Types Registry scripts include types for their configuration and exposed API, so editors can complete calls such as `gtag()`{lang="ts"} without a separate global declaration. ### Development-time validation Registry scripts use [Valibot schemas](https://valibot.dev/guides/schemas/) to validate configuration during development. For example, the Cloudflare Web Analytics schema rejects a token shorter than 32 characters. ::code-group ```ts [Schema] export const CloudflareWebAnalyticsOptions = object({ /** * The Cloudflare Web Analytics token. */ token: pipe(string(), minLength(32)), /** * Cloudflare Web Analytics enables measuring SPAs automatically by overriding the History APIโ€™s pushState function * and listening to the onpopstate. Hash-based router is not supported. * * @default true */ spa: optional(boolean()), }) ``` ```ts [Example] useScriptCloudflareWebAnalytics({ token: '123', // skipped in development because the token is too short }) ``` :: Production builds omit the validation code. During development, Nuxt Scripts skips an invalid script and logs the schema issues. ### Runtime config Register a script in `nuxt.config.ts` and Nuxt Scripts creates public runtime config fields for the environment-backed inputs declared by that integration. You can then supply fields such as its ID or token through `.env` instead of hardcoding them. ::code-group ```text [.env] NUXT_PUBLIC_SCRIPTS_CLOUDFLARE_WEB_ANALYTICS_TOKEN=YOUR_TOKEN ``` ```ts [nuxt.config.ts] export default defineNuxtConfig({ scripts: { registry: { cloudflareWebAnalytics: { trigger: 'client' }, }, }, }) ``` :: ## Usage ### Disabling in development Set a registry entry to `mock` when development code calls an API such as `gtag`, but you do not want to load the vendor script. Mock mode registers a manual context and skips option validation. ```ts [nuxt.config.ts] export default defineNuxtConfig({ scripts: { registry: { googleTagManager: { trigger: 'onNuxtReady' }, }, }, $development: { scripts: { registry: { googleTagManager: 'mock', }, }, }, }) ``` ### Load multiple instances Registry scripts deduplicate by `src` or `key`. Give each call a unique `key` when you need multiple instances with different configuration. ```ts const { proxy: gaOne } = useScriptGoogleAnalytics({ id: 'G-TR58L0EF8P', }) const { proxy: gaTwo } = useScriptGoogleAnalytics({ // without a key the first script instance will be returned key: 'gtag2', id: 'G-1234567890', }) ``` A custom key also changes the runtime config path. For `key: 'gtag2'`, declare the matching path yourself: ```ts export default defineNuxtConfig({ runtimeConfig: { public: { scripts: { gtag2: { id: '', // NUXT_PUBLIC_SCRIPTS_GTAG2_ID }, }, }, }, }) ``` ### Use script options and script input Registry scripts expose the core [`useScript()`{lang="ts"}](/docs/api/use-script){lang="ts"} inputs through two fields: - `scriptOptions`: [useScript options](/docs/api/use-script#nuxtusescriptoptions), such as `trigger`. - `scriptInput`: [script element input](/docs/api/use-script#usescriptinput), such as `data-*` attributes. ```ts import { useTimeout } from '@vueuse/core' import { useScriptGoogleAnalytics } from '#imports' const ready = useTimeout(5000) useScriptGoogleAnalytics({ id: 'G-XXXXXXXX', // HTML attributes to pass to the script element scriptInput: { 'data-test': 'true', }, // useScript options used for advanced features scriptOptions: { trigger: ready, }, }) ``` ### Reuse one instance If several pages use the same integration, configure it once in `app.vue` or `nuxt.config`. Later composable calls return that script instance and do not need the options again. ::code-group ```ts [nuxt.config.ts] export default defineNuxtConfig({ scripts: { registry: { // The explicit trigger loads the script globally. fathomAnalytics: { site: 'SITE_ID', trigger: 'onNuxtReady', } } } }) ``` ```vue [components/any-component.vue] ``` :: You can also keep the shared configuration in your own composable: ```ts export function useFathomAnalytics() { return useScriptFathomAnalytics({ site: 'SITE_ID', }) } ``` ## Extending the Script Registry Use the `scripts:registry` hook in `nuxt.config.ts` to add an integration: ```ts [nuxt.config.ts] import { createResolver } from '@nuxt/kit' const { resolve } = createResolver(import.meta.url) export default defineNuxtConfig({ modules: ['@nuxt/scripts'], hooks: { 'scripts:registry': function (registry) { registry.push({ category: 'custom', label: 'My Custom Analytics', logo: '...', // optional import: { name: 'useScriptMyAnalytics', from: resolve('./composables/useScriptMyAnalytics'), }, }) }, }, devtools: { enabled: true, }, }) ``` Then create your custom script composable: ```ts [composables/useScriptMyAnalytics.ts] import type { RegistryScriptInput } from '#nuxt-scripts/types' import { object, string } from 'valibot' import { useRegistryScript } from '#nuxt-scripts/utils' export interface MyAnalyticsApi { track: (event: string, data?: Record) => void identify: (userId: string) => void } declare global { interface Window { MyAnalytics: MyAnalyticsApi & { init: (apiKey?: string) => void } } } // Schema for validation and DevTools metadata export const MyAnalyticsSchema = object({ apiKey: string(), }) export type MyAnalyticsInput = RegistryScriptInput export function useScriptMyAnalytics(options?: MyAnalyticsInput) { return useRegistryScript('myAnalytics', resolvedOptions => ({ scriptInput: { src: 'https://analytics.example.com/sdk.js', }, schema: import.meta.dev ? MyAnalyticsSchema : undefined, scriptOptions: { ...options?.scriptOptions, use() { if (!window.MyAnalytics) return window.MyAnalytics.init(resolvedOptions.apiKey) return window.MyAnalytics as T }, }, }), options) } ``` ### Using Custom Registry Scripts Nuxt auto-imports the registered composable: ```vue [pages/index.vue] ``` ### DevTools Integration When you include a validation schema, Nuxt Scripts uses its required fields to populate the script's DevTools metadata in development. --- - **Page:** Google Analytics - **Source:** https://scripts.nuxt.com/scripts/google-analytics - **Description:** Send Google Analytics page views and events with typed consent controls. [Google Analytics](https://marketingplatform.google.com/about/analytics/) records page views and events for traffic and audience analysis. ::script-stats :: ::script-docs :: ### Usage Call [`gtag`](https://developers.google.com/tag-platform/gtagjs/reference) through the script [proxy](/docs/guides/key-concepts#understanding-proxied-functions): ```ts const { proxy } = useScriptGoogleAnalytics({ id: 'G-XXXXXXXX' }) proxy.gtag('event', 'page_view') ``` The proxy also exposes `dataLayer`. Examples below that omit `id` assume it is supplied through `scripts.registry.googleAnalytics`. ## Consent Mode Google Analytics accepts [GCMv2 consent state](https://developers.google.com/tag-platform/security/guides/consent). `defaultConsent` fires before `gtag('js', ...)`{lang="ts"}; use `consent.update()`{lang="ts"} for later choices. Calling `consent.default()`{lang="ts"} after the composable returns queues a new default, but it cannot reproduce that pre-initialization ordering. ::callout{icon="i-heroicons-play" to="https://stackblitz.com/github/nuxt/scripts/tree/main/examples/regional-consent" target="_blank"} Open the [regional consent example](https://stackblitz.com/github/nuxt/scripts/tree/main/examples/regional-consent) on [StackBlitz](https://stackblitz.com). :: ```vue ``` `consent.update()`{lang="ts"} and `consent.default()`{lang="ts"} both accept any `Partial`{lang="ts"}; missing categories stay at their current value. Both methods validate input against the canonical GCMv2 schema and warn via `consola` on unknown keys or non-`granted`/`denied` values. For pre-`gtag('js')`{lang="ts"} setup beyond consent defaults, `onBeforeGtagStart` remains available as a general escape hatch. ### Per-region defaults Pass an array to `defaultConsent` to fire one `gtag('consent','default', state)`{lang="ts"} per entry. This matches Google's [region-specific consent pattern](https://developers.google.com/tag-platform/security/guides/consent?consentmode=advanced#region-specific-behavior): more specific regions (e.g. `US-CA`) override broader ones (`US`); an entry with no `region` is the unscoped global fallback. ```vue ``` The module forwards each entry verbatim, in input order. Precedence between region-scoped and unscoped defaults is enforced by gtag at runtime, not by ordering. ## Customer-specific GA properties For a marketplace or multi-tenant app, a second tag can send events to the customer's GA property: ```vue [ProductPage.vue] ``` ## Custom Dimensions and User Properties Google documents the scopes for [`config`, `set`, and event parameters](https://developers.google.com/tag-platform/gtagjs/reference#parameter_scope). Use `config` for values tied to one GA property and `set` for global defaults. ```ts const { proxy } = useScriptGoogleAnalytics() // User properties for this GA4 property proxy.gtag('config', 'G-XXXXXXXX', { user_properties: { user_tier: 'premium', account_type: 'business', }, }) // Event with custom dimensions (register in GA4 Admin > Custom Definitions) proxy.gtag('event', 'purchase', { transaction_id: 'T12345', value: 99.99, payment_method: 'credit_card', // custom dimension discount_code: 'SAVE10' // custom dimension }) // Default params for all future events proxy.gtag('set', { country: 'US', currency: 'USD' }) ``` ## Tracking SPA page views The registry configures the Google tag and sends its initial page view. `useScriptEventPage` listens to Nuxt's `page:finish` hook, including the initial client render when you register it early enough. In a long-lived component such as `app.vue`, ignore that initial callback but do not accidentally drop the first navigation when registration happens later. Keep the previous URL as `page_referrer`, as described in Google's [SPA measurement guide](https://developers.google.com/analytics/devguides/collection/ga4/single-page-applications): ```ts const { proxy } = useScriptGoogleAnalytics() const initialPath = useRoute().fullPath let initialPageSeen = false let previousLocation: string | undefined useScriptEventPage(({ title, path }) => { const pageLocation = new URL(path, window.location.origin).href // page:finish may emit for the initial client render. if (!initialPageSeen && path === initialPath) { initialPageSeen = true previousLocation = pageLocation return } previousLocation ||= new URL(initialPath, window.location.origin).href proxy.gtag('event', 'page_view', { page_title: title, page_location: pageLocation, page_referrer: previousLocation, }) previousLocation = pageLocation }) ``` Do not add manual page views if your web stream's enhanced measurement already [tracks browser history changes](https://developers.google.com/analytics/devguides/collection/ga4/views), or you will record duplicate events. ## Proxy queue The proxy queues `gtag` calls until the script loads and preserves their order. ```ts const { proxy, onLoaded } = useScriptGoogleAnalytics() // Fire-and-forget (queued until GA loads) proxy.gtag('event', 'cta_click', { button_id: 'hero-signup' }) // Need return value? Wait for load onLoaded(({ gtag }) => { gtag('get', 'G-XXXXXXXX', 'client_id', id => console.log(id)) }) ``` ## Common events Use Google's [recommended events](https://developers.google.com/analytics/devguides/collection/ga4/events) when one matches the action; custom names remain available for app-specific behavior. ```ts const { proxy } = useScriptGoogleAnalytics() // E-commerce proxy.gtag('event', 'purchase', { transaction_id: 'T_12345', value: 59.98, currency: 'USD', items: [{ item_id: 'SKU_12345', item_name: 'Widget', price: 29.99, quantity: 2 }] }) // Engagement proxy.gtag('event', 'login', { method: 'Google' }) proxy.gtag('event', 'search', { search_term: 'nuxt scripts' }) // Custom proxy.gtag('event', 'feature_used', { feature_name: 'dark_mode' }) ``` ## Debugging Enable debug mode for a tag configuration: ```ts proxy.gtag('config', 'G-XXXXXXXX', { debug_mode: true }) ``` Use [DebugView, Tag Assistant, or your browser's Network panel](https://developers.google.com/analytics/devguides/collection/ga4/troubleshoot) to verify the tag. In the Network panel, look for successful requests to `google-analytics.com/g/collect` or `analytics.google.com/g/collect`. For consent mode setup, see the [Consent Guide](/docs/guides/consent). ::script-types :: --- - **Page:** Best Privacy-First Analytics Compared: Plausible vs Fathom vs Umami + 4 More - **Source:** https://scripts.nuxt.com/learn/privacy-first-analytics-compared - **Description:** We ran AST analysis on 7 privacy-first analytics tools and catalogued exactly which browser APIs each one touches. See how Plausible, Fathom, Umami, Cloudflare, Rybbit, Databuddy, and Vercel Analytics compare on privacy, performance, and GDPR compliance. "Privacy-first" is a marketing label. The code tells a different story. We decompiled every privacy-first analytics script in the [Nuxt Scripts](https://github.com/nuxt/scripts) registry and ran AST analysis to catalogue exactly which browser APIs each one touches at runtime. No marketing claims, no privacy policy excerpts - what the script does. The finding: **even among the best privacy-first analytics tools, the API surface ranges from 2 to 7 browser APIs.** That gap determines what data leaves your users' browsers. ## How We Compared These 7 Privacy-First Analytics Tools ::tracking-matrix :: All seven providers share one critical property: **zero cookies**. That's the baseline for any privacy-first Google Analytics alternative. But the similarity ends there. The tools fall into three groups based on how many browser APIs they access: - **Minimal (2 APIs):** :LinkScript{slug="plausibleAnalytics"}, :LinkScript{slug="fathomAnalytics"}, :LinkScript{slug="vercelAnalytics"} - **Medium (3โ€“4 APIs):** :LinkScript{slug="umamiAnalytics"}, :LinkScript{slug="cloudflareWebAnalytics"} - **Broader (6โ€“8 APIs):** :LinkScript{slug="rybbitAnalytics"}, :LinkScript{slug="databuddyAnalytics"} None of these providers use fingerprinting APIs (canvas, WebGL, audioContext) or set cookies. The differences are in how much device and session context they collect alongside pageview data. Rybbit and Databuddy also use `localStorage`{lang="ts"} for persistent identifiers - still cookie-free, but with cross-session tracking capability. ::note For comparison, GA4 uses 8 APIs including `cookies`{lang="ts"}, `mutationObserver`{lang="ts"}, and `intersectionObserver`{lang="ts"}. The best privacy-first analytics tools use 2โ€“8x fewer APIs than traditional analytics. See the [full performance comparison](/learn/analytics-script-performance) for size and execution benchmarks. :: ## Plausible Analytics Review :LinkScript{slug="plausibleAnalytics"} is the most popular privacy-first Google Analytics alternative. It touches **2 browser APIs**: `localStorage`{lang="ts"} (for session deduplication) and `fetch`{lang="ts"}. ::analytics-review ```yaml [props] scriptKey: plausibleAnalytics privacy: Generates a daily hash from the visitor's IP, user agent, and a rotating salt. Plausible purges the salt every 24 hours, making it impossible to reconstruct cross-day visitor chains - satisfying GDPR's anonymization threshold. selfHosting: Available. The most minimal footprint of any self-hostable privacy analytics option. bestFor: Teams that want the smallest privacy surface with the best standalone dashboard. ``` :: ## Fathom Analytics Review :LinkScript{slug="fathomAnalytics"} matches Plausible's minimal API surface with **2 APIs**: `localStorage`{lang="ts"} and `sendBeacon`{lang="ts"}. ::analytics-review ```yaml [props] scriptKey: fathomAnalytics privacy: EU Isolation routes all EU visitor data through EU-based infrastructure, adding a layer of DPA compliance beyond the cookie-free baseline. selfHosting: No (SaaS only). bestFor: High-traffic sites that want flat pricing and strong EU data residency guarantees. ``` :: ## Umami Analytics Review :LinkScript{slug="umamiAnalytics"} is an open-source, self-hostable privacy analytics tool. It uses **3 APIs**: `localStorage`{lang="ts"}, `fetch`{lang="ts"}, and `screen`{lang="ts"} (for viewport dimensions). ::analytics-review ```yaml [props] scriptKey: umamiAnalytics privacy: Cookie-free, no persistent identifiers. Collects screen dimensions on top of the minimal baseline. selfHosting: MIT licensed, supports PostgreSQL or MySQL. Lowest operational overhead of any self-hosted option. bestFor: Developers who want full data ownership with minimal operational cost. ``` :: ## Cloudflare Web Analytics Review :LinkScript{slug="cloudflareWebAnalytics"} uses **4 APIs**: `userAgent`{lang="ts"}, `sendBeacon`{lang="ts"}, `xhr`{lang="ts"}, and `performanceObserver`{lang="ts"} - the extra APIs power built-in Core Web Vitals collection. ::analytics-review ```yaml [props] scriptKey: cloudflareWebAnalytics privacy: Cookie-free. Cloudflare processes data on their infrastructure but does not sell or share it. selfHosting: No (tied to Cloudflare). bestFor: Sites already on Cloudflare that want free analytics with built-in CWV monitoring. ``` :: ## Rybbit Analytics Review :LinkScript{slug="rybbitAnalytics"} is an open-source privacy analytics tool that touches **6 APIs** including `localStorage`{lang="ts"}, `performanceObserver`{lang="ts"}, `screen`{lang="ts"}, and `languages`{lang="ts"}. ::analytics-review ```yaml [props] scriptKey: rybbitAnalytics privacy: Cookie-free. Uses localStorage for user IDs, which persists across sessions until manually cleared. Includes CWV collection. selfHosting: Open-source and self-hostable. bestFor: Teams that want self-hosted analytics with Core Web Vitals built in. ``` :: ## Databuddy Analytics Review :LinkScript{slug="databuddyAnalytics"} touches the most APIs at **8**: `localStorage`{lang="ts"}, `sessionStorage`{lang="ts"}, `performanceObserver`{lang="ts"}, `screen`{lang="ts"}, `languages`{lang="ts"}, `userAgent`{lang="ts"}, `sendBeacon`{lang="ts"}, and `fetch`{lang="ts"}. ::analytics-review ```yaml [props] scriptKey: databuddyAnalytics privacy: Cookie-free. Uses both localStorage and sessionStorage for state. Broader device signal collection than other privacy-first tools, but no fingerprinting APIs. selfHosting: Open-source and self-hostable. bestFor: Teams that want the most device context possible while staying cookie-free. ``` :: ## Vercel Analytics Review :LinkScript{slug="vercelAnalytics"} is the most minimal option, touching **2 APIs**: `userAgent`{lang="ts"} (device classification) and `fetch`{lang="ts"}. ::analytics-review ```yaml [props] scriptKey: vercelAnalytics privacy: Cookie-free by default. Minimal data collection - no localStorage, no session state. An opt-in enableCookie flag exists but defaults to off. selfHosting: No (tied to Vercel platform). bestFor: Vercel-hosted apps that want zero-config analytics with the absolute smallest footprint. ``` :: ## Performance Comparison: All 7 Privacy-First Analytics Tools Fewer APIs and smaller scripts translate directly to less main-thread work: ::privacy-performance-table :: Every privacy-first analytics provider stays under **2ms of script execution** and **10ms of task duration**. The three providers that collect Core Web Vitals (Cloudflare, Rybbit, Databuddy) carry larger decoded sizes (25โ€“30 KB) because the `performanceObserver`{lang="ts"} integration adds measurement code. That's a reasonable tradeoff if you want CWV data without a separate monitoring tool. ## GDPR Compliance: Why Cookie-Free Analytics Win in 2026 ### The Consent Banner Problem EU cookie acceptance rates dropped to [40โ€“58% across member states](https://www.cnil.fr/en/cookies-and-other-tracking-devices-how-do-i-comply-with-the-rules) in 2026 (CNIL guidance, February 2026). Cookie-based analytics like GA4 and default Matomo lose 40โ€“60% of their traffic data to "Reject All" clicks. All seven privacy-first analytics tools in this comparison capture close to 100%. ::chart-consent-impact :: ### Matomo's CNIL Exemption [Matomo is the only analytics platform](https://www.cnil.fr/en/cookies-and-other-tracking-devices) with a formal consent exemption from France's data protection authority. The exemption requires IP masking, 13-month cookie caps, and no third-party data sharing. However, Matomo's default configuration uses cookies - you must explicitly configure cookie-free mode to qualify. ## First-Party Mode: Recovering Ad-Blocked Data 25โ€“45% of users block trackers ([Blockthrough Ad-Block Report 2026](https://blockthrough.com)). Even privacy-first scripts hosted on known domains (`plausible.io`, `cloud.umami.is`) get blocked at the DNS level. Nuxt Scripts' first-party mode proxies analytics traffic through your own domain: ```ts // nuxt.config.ts export default defineNuxtConfig({ scripts: { registry: { plausibleAnalytics: { domain: 'yourdomain.com' } } } }) ``` Requests go to `yourdomain.com/_scripts/plausible` instead of `plausible.io/js/script.js`. Indistinguishable from a first-party request to most blockers. Data accuracy returns to near 100%. ## Which Privacy-First Analytics Tool Should You Choose? ::privacy-picker-table :: For most apps: **start with :LinkScript{slug="plausibleAnalytics"}, :LinkScript{slug="fathomAnalytics"}, or :LinkScript{slug="vercelAnalytics"}**. Add :LinkScript{slug="cloudflareWebAnalytics"} or :LinkScript{slug="rybbitAnalytics"} if you need Core Web Vitals. Only reach for GA4 or Matomo if you need specific features that privacy-first analytics tools don't offer. --- **Last reviewed:** March 2026 ### Related Resources - [Analytics Script Performance Comparison](/learn/analytics-script-performance) - [Choosing the Right Analytics for Nuxt](/learn/choosing-analytics-nuxt) - [Nuxt Scripts Documentation](/docs/getting-started) --- - **Page:** useScript - **Source:** https://scripts.nuxt.com/docs/v0/api/use-script - **Description:** API documentation for the useScript function. This composable is a wrapper around the Unhead [useScript](https://unhead.unjs.io/usage/composables/use-script) with extra Nuxt goodies on top, for full documentation please refer to this. ## Signature ```ts export function useScript>(input: UseScriptInput, options?: NuxtUseScriptOptions): T & { $script: Promise & VueScriptInstance } {} ``` ## Arguments ### `UseScriptInput` This is the script tag input. For example, you can pass a URL string or an object with the script tag attributes. ```ts export type UseScriptInput = string | { src?: string async?: boolean defer?: boolean type?: string integrity?: string crossorigin?: string text?: string innerHTML?: string innerText?: string content?: string referrerPolicy?: string attributes?: Record } ``` See the [Script Input](https://unhead.unjs.io/usage/composables/use-script#argument-script-options) documentation for more information on the options. ### `NuxtUseScriptOptions` See the [Script Options](https://unhead.unjs.io/usage/composables/use-script#argument-use-script-options) documentation for more information on the options. - `use` - The function to resolve the script. - `trigger` - [Triggering Script Loading](/docs/guides/script-triggers) - `bundle` - [Bundling Remote Scripts](/docs/guides/bundling) for more information. ```ts export type NuxtUseScriptOptions = Omit, 'trigger'> & { /** * The trigger to load the script: * - `onNuxtReady` - Load the script when Nuxt is ready. * - `manual` - Load the script manually by calling `load()` * - `Promise` - Load the script when the promise resolves. */ trigger?: UseScriptOptions['trigger'] | 'onNuxtReady' /** * Should the script be bundled as an asset and loaded from your server. This is useful for improving the * performance by avoiding the extra DNS lookup and reducing the number of requests. It also * improves privacy by not sharing the user's IP address with third-party servers. * - `true` - Bundle the script as an asset. * - `false` - Do not bundle the script. (default) */ bundle?: boolean /** * Skip any schema validation for the script input. This is useful for loading the script stubs for development without * loading the actual script and not getting warnings. */ skipValidation?: boolean } ``` ## Return See the [Understanding proxied functions](/docs/guides/key-concepts) and [$script](https://unhead.unjs.io/usage/composables/use-script#argument-use-script-options) documentation for more information on the return. --- - **Page:** Choosing Analytics for Your Nuxt App: A Data-Backed Guide - **Source:** https://scripts.nuxt.com/learn/choosing-analytics-nuxt - **Description:** Stop choosing analytics by brand. We measured transfer size, execution cost, and privacy profiles of every major provider to help you pick the right one for your Nuxt app. Most teams pick analytics based on name recognition. GA4 because everyone uses it. Plausible because a tweet said it was good. PostHog because the free tier looked generous. That's backwards. The right analytics tool depends on what you need to track - and in Nuxt, on how well it handles SSR hydration and SPA navigation. We measured the transfer size, execution cost, and privacy profiles of every major provider. Here's what the data says. ## What Do You Need? Answer this before looking at any provider. Each step up adds complexity, weight, and cost. 1. **"I want visitor counts."** - :LinkScript{slug="cloudflareWebAnalytics"} or :LinkScript{slug="plausibleAnalytics"}. 2. **"I need custom events - clicks, downloads, signups."** - :LinkScript{slug="plausibleAnalytics"}, :LinkScript{slug="umamiAnalytics"}, or :LinkScript{slug="fathomAnalytics"}. 3. **"I need ad ROI and conversion funnels."** - :LinkScript{slug="fathomAnalytics"} or :LinkScript{slug="googleAnalytics"}. 4. **"I need session replays and user identity."** - :LinkScript{slug="posthog"} or LogRocket. 5. **"I need to route data to 20 tools and a data warehouse."** - :LinkScript{slug="segment"}. Most sites need tier 1 or 2. The temptation to jump to tier 5 because "we might need it" is expensive and usually wrong. ## The Nuxt Analytics Matrix (2026) ::recommendation-table :: **Practical advice:** If you're building a SaaS product, the hybrid stack in section 7 beats any single tool. If you're building a marketing site, :LinkScript{slug="plausibleAnalytics"} is the right default. ## Nuxt-Specific Gotchas Nuxt's hybrid SSR/SPA architecture breaks standard analytics snippets in two ways. ### SSR Hydration Double-Counting Place a tracking snippet in `app.html` or `useHead` and it fires twice: once when the browser parses the server-rendered HTML, again when Vue hydrates. Your pageview counts inflate by up to 2x for initial loads. ([Nuxt Scripts docs](/docs/guides/key-concepts)) Nuxt Scripts loads analytics `onNuxtReady` by default. The script only runs once the app is fully interactive on the client. ### SPA Navigation Tracking Standard scripts listen for `window.load`. In a Nuxt SPA, the page never reloads as you navigate between routes. Without router integration, you're tracking one pageview per session regardless of how many pages the user visits. Nuxt Scripts' registry composables (like `useScriptPlausible`) automatically hook into the Nuxt router and send virtual pageviews on every navigation. ::warning Both issues affect every analytics provider. Don't assume your current setup is tracking correctly without verifying against server-side logs. :: ## Setup: 1 Line vs. \~40 Lines ::code-group ```ts [Nuxt Scripts (Recommended)] // nuxt.config.ts export default defineNuxtConfig({ scripts: { registry: { plausibleAnalytics: { domain: 'example.com' } } } }) ``` ```ts [Manual plugin] // plugins/analytics.client.ts import { defineNuxtPlugin, useRouter } from '#app' export default defineNuxtPlugin(() => { // Must guard against SSR if (!process.client) return const script = document.createElement('script') script.src = 'https://plausible.io/js/script.js' script.setAttribute('data-domain', 'example.com') script.defer = true document.head.appendChild(script) // Must manually watch route changes const router = useRouter() router.afterEach((to) => { window.plausible?.('pageview', { u: to.fullPath }) }) }) ``` :: The manual approach also misses the hydration fix. You'd need to delay the script manually - and that's before handling consent, first-party proxying, or TypeScript types. ## 2026 Pricing at 100k Pageviews/Month ::pricing-table :: ::chart-pricing :: GA4's "free" tier comes with tradeoffs: raw event access requires a [BigQuery export](https://support.google.com/analytics/answer/9358801?hl=en), standard properties have daily export limits, and retention is configurable only within product limits. For teams that need direct SQL access to their own data, Umami (self-hosted) or Plausible may be cheaper in the long run. Migrating off GA4 is also easier than it looks; both [Matomo](https://matomo.org/docs/google-analytics-importer/) and Plausible support importing historical GA data. ## The Ad-Blocker Problem 25-45% of users block trackers ([Blockthrough Ad-Block Report 2026](https://blockthrough.com)). Scripts hosted on `google-analytics.com`, `plausible.io`, or any known tracker domain get blocked at the DNS or network level. ::chart-consent-impact :: Nuxt Scripts' first-party mode proxies all analytics traffic through your own domain, saving 200-500ms in addition to recovering blocked data ([Nuxt Scripts benchmarks](/learn/analytics-script-performance)): ```ts [nuxt.config.ts] export default defineNuxtConfig({ scripts: { firstParty: true, registry: { plausibleAnalytics: { domain: 'example.com' } } } }) ``` Requests go to `yoursite.com/_scripts/...` instead of `plausible.io`. To a browser or DNS blocker, they're indistinguishable from regular site traffic. Data accuracy returns to near 100%. This also has a privacy benefit: third parties see your server's IP instead of your users'. ::note First-party mode supports Google Analytics, GTM, Meta Pixel, Plausible, Fathom, Segment, Clarity, Hotjar, and more. :: ## The Hybrid Stack You don't have to pick one tool. High-performing Nuxt teams typically run two: - **:LinkScript{slug="plausibleAnalytics"} or :LinkScript{slug="umamiAnalytics"}** for traffic stats. Fast, readable dashboard that anyone on the team can use without training. Answers "how many people visited, from where, and which pages." - **:LinkScript{slug="posthog"}** for product and engineering. Session replay, feature flags, A/B tests, and funnel analysis. Answers "what did users do and where did they drop off." The two tools don't overlap. :LinkScript{slug="plausibleAnalytics"}/:LinkScript covers the marketing and content team's questions. :LinkScript{slug="posthog"} covers the product team's. ```ts [nuxt.config.ts] export default defineNuxtConfig({ scripts: { firstParty: true, registry: { plausibleAnalytics: { domain: 'example.com' }, posthog: { apiKey: 'phc_XXXXXX' } } } }) ``` Both load deferred, both get first-party proxying, and neither requires a manual router hook. ## Making the Call Three questions narrow it down: 1. **Who reads the data?** Marketing team - use :LinkScript{slug="plausibleAnalytics"}. Engineers debugging sessions - use :LinkScript{slug="posthog"}. Google Ads team - use :LinkScript{slug="googleAnalytics"}. 2. **What's your compliance situation?** EU users with no cookie consent UI - use :LinkScript{slug="plausibleAnalytics"} or :LinkScript{slug="umamiAnalytics"} (cookie-free). GDPR consent required - all providers work, Plausible and Umami need the least configuration. 3. **What's your scale?** Under 1M events/month - :LinkScript{slug="posthog"} is free. Under 100k events/month - :LinkScript{slug="umamiAnalytics"} Cloud is free. High-volume marketing site - :LinkScript{slug="fathomAnalytics"}'s flat pricing avoids surprise bills. For most Nuxt apps: start with :LinkScript{slug="plausibleAnalytics"} for traffic stats. Add :LinkScript{slug="posthog"} when you need product analytics. Use Nuxt Scripts for both - router integration, SSR safety, and first-party proxying with no glue code required. --- - **Page:** Script Triggers - **Source:** https://scripts.nuxt.com/docs/guides/script-triggers - **Description:** Control when Nuxt Scripts starts loading a script. ::callout{icon="i-heroicons-play" to="https://stackblitz.com/github/nuxt/scripts/tree/main/examples/performance" target="_blank"} Try the live [Performance Example](https://stackblitz.com/github/nuxt/scripts/tree/main/examples/performance) on [StackBlitz](https://stackblitz.com) to see triggers in action. :: The `trigger` option controls when a script starts loading. ## How Triggers Work Pass a supported trigger source as `trigger`: the script loads when a reactive source becomes truthy, or when a promise resolves to `undefined` or a truthy value. A promise that resolves to `false` leaves the script unloaded. ```ts const shouldLoad = ref(false) useScript('https://example.com/script.js', { trigger: shouldLoad }) // Later: trigger loading shouldLoad.value = true ``` This works with refs, computed refs, getter functions, and promises: ```ts // Ref trigger: shouldLoad // Computed trigger: computed(() => !!route.query.affiliateId) // Getter function trigger: () => shouldLoad.value // Promise trigger: new Promise(resolve => setTimeout(resolve, 3000)) ``` ## Default: onNuxtReady By default, scripts use the [`onNuxtReady`](https://nuxt.com/docs/api/utils/on-nuxt-ready) trigger. Nuxt waits for hydration, then schedules the load with `requestIdleCallback` or a short timer fallback. ```ts // The default, written explicitly useScript('https://widget.intercom.io/widget/abc123', { trigger: 'onNuxtReady' }) // Registry composables inherit onNuxtReady by default useScriptGoogleAnalytics({ id: 'GA_MEASUREMENT_ID', // trigger: 'onNuxtReady' is implied }) ``` Registry composables inherit this default unless an integration defines an earlier trigger. A registry entry in `nuxt.config` still needs an explicit `trigger` to create a global instance; an entry without one only enables its types, bundling, proxy routes, and other infrastructure. ::callout{color="amber"} `partytown: true` is an exception. The current Partytown path inserts the script during SSR and reports it as loaded, so it does not wait for `trigger`. A registry entry still needs a truthy `trigger` to generate the global instance, but the trigger value does not control when that Partytown script runs. :: You can change this default by modifying the [defaultScriptOptions](/docs/api/nuxt-config#defaultscriptoptions). ## Specialized Triggers ### Idle Timeout [`useScriptTriggerIdleTimeout()`{lang="ts"}](/docs/api/use-script-trigger-idle-timeout){lang="ts"} starts its timer after Nuxt is ready: ::code-group ```ts [Composable] useScript('https://example.com/analytics.js', { trigger: useScriptTriggerIdleTimeout({ timeout: 5000 }) }) ``` ```ts [nuxt.config.ts] export default defineNuxtConfig({ scripts: { registry: { googleAnalytics: { id: 'GA_MEASUREMENT_ID', trigger: { idleTimeout: 3000 }, } } } }) ``` :: ### User Interaction [`useScriptTriggerInteraction()`{lang="ts"}](/docs/api/use-script-trigger-interaction){lang="ts"} loads on the first configured interaction: ::code-group ```ts [Composable] useScript('https://example.com/chat-widget.js', { trigger: useScriptTriggerInteraction({ events: ['scroll', 'click', 'keydown'] }) }) ``` ```ts [nuxt.config.ts] export default defineNuxtConfig({ scripts: { globals: { chatWidget: ['https://widget.example.com/chat.js', { trigger: { interaction: ['scroll', 'click', 'touchstart'] } }] } } }) ``` :: ### Element Event Triggers [`useScriptTriggerElement()`{lang="ts"}](/docs/api/use-script-trigger-element){lang="ts"} watches one element for visibility or events: ```ts const buttonEl = ref() useScript('https://example.com/feature.js', { trigger: useScriptTriggerElement({ trigger: 'visible', // or 'mouseover', 'click', etc. el: buttonEl, }) }) ``` ## Basic Triggers ### Manual Control Use the `manual` trigger when your code should call `load()`{lang="ts"} directly: ```ts const { load } = useScript('https://example.com/script.js', { trigger: 'manual' }) // Load when you decide await load() ``` --- - **Page:** Installation - **Source:** https://scripts.nuxt.com/docs/v0/getting-started/installation - **Description:** Learn how to create a Nuxt Scripts project or add it to your current Nuxt project. ## Quick Start To get started, simply run: ```bash npx nuxi@latest module add scripts ``` That's it! The Nuxt Scripts module should be downloaded and added to your Nuxt Config `modules`. ## Next Steps Need some inspiration to start using Nuxt Scripts? Try out the following: 1. ๐ŸŽ‰ Make it rain emojis with the [Confetti Tutorial](/docs/getting-started/confetti-tutorial). 2. ๐Ÿ“š Learn about how the [Script Loading](/docs/guides/script-triggers) works. 3. ๐Ÿ” Explore the [Script Registry](/scripts) for popular pre-configured third-party scripts. 4. ๐Ÿš€ Load other scripts with [useScript](/docs/api/use-script) or [Global Scripts](/docs/guides/global). 5. ๐Ÿ”จ Fine-tune your performance and privacy with [Bundling](/docs/guides/bundling) and [Consent Management](/docs/guides/consent). --- - **Page:** Contributing - **Source:** https://scripts.nuxt.com/docs/getting-started/contributing - **Description:** Set up the Nuxt Scripts repository for local development. ## Prepare your environment `@nuxt/scripts` is a [pnpm](https://pnpm.io) workspace. Use pnpm to install and link its dependencies. After cloning the repository, run: ```sh pnpm i && pnpm dev:prepare ``` ## Run the playground Start the playground development server with: ```sh pnpm dev ``` ## Check your changes Run the repository's lint and test commands before submitting a change: ```sh pnpm lint pnpm test:run ``` --- - **Page:** Submitting an issue - **Source:** https://scripts.nuxt.com/docs/getting-started/reproductions - **Description:** Use StackBlitz to create reproductions when troubleshooting issues with the module. Include a minimal reproduction when you open an issue against [Nuxt Scripts](https://github.com/nuxt/scripts). ## Create a reproduction A reproduction separates the module problem from the rest of your application. Nuxt's [bug-reporting guide](https://nuxt.com/docs/4.x/community/reporting-bugs#create-a-minimal-reproduction) explains how to isolate the problem and remove unrelated code. Start from the [StackBlitz](https://stackblitz.com) playground below. You can test the module there before trimming the project down for an issue. ## StackBlitz playground - [Minimal Nuxt Scripts](https://stackblitz.com/edit/nuxt-starter-pkwfkx?file=pages%2Findex.vue) --- - **Page:** Google Adsense - **Source:** https://scripts.nuxt.com/scripts/v0/ads/google-adsense - **Description:** Show Google Adsense ads in your Nuxt app. [Google AdSense](https://www.google.com/adsense/start/) allows you to monetize your website by displaying relevant ads from Google. Nuxt Scripts provides: - `useScriptGoogleAdsense`: A composable to manage Google AdSense dynamically. - ``: A headless component to embed ads directly in your Nuxt app. ## Global Setup You can configure Google AdSense **globally** in your `nuxt.config.ts` so that the script is automatically loaded on all pages. ```ts [nuxt.config.ts] export default defineNuxtConfig({ scripts: { registry: { googleAdsense: { client: "ca-pub-", // Your Google AdSense Publisher ID autoAds: true, // Enable Auto Ads }, }, }, }); ``` ## Where to Find `` (Publisher ID) Your **Google AdSense Publisher ID** (also known as `ca-pub-XXXXXXX`) can be found in your **Google AdSense Account**: 1. Log in to your **Google AdSense** account. 2. Navigate to **Account > Settings** (click on your profile icon > "Account information"). 3. Locate the **Publisher ID** under **Account Information**. 4. Replace `` in the config above with your actual ID. ::callout{icon="i-heroicons-light-bulb" to="https://adsense.google.com/start/" target="_blank"} You can also manage **Auto Ads settings** from your **Google AdSense Dashboard** to control *ad types, placements, and revenue optimization*. :: ## Site Ownership Verification ### Automatic Meta Tag Insertion If a `client` is provided, a **meta tag** will be inserted on the page **automatically** for Google to verify your site ownership. ::tabs :::div{label="Example" icon="i-heroicons-code-bracket-square"} ```ts [nuxt.config.ts] export default defineNuxtConfig({ scripts: { registry: { googleAdsense: { client: "ca-pub-", // AdSense Publisher ID }, }, }, }); ``` ::: :::div{label="Output" icon="i-heroicons-magnifying-glass-circle"} ```html ``` ::: :: ### Using `ads.txt` for Verification Google recommends adding an `ads.txt` file for **ad revenue eligibility**. #### Steps: 1. Create a new file: `public/ads.txt` 2. Add the following content: ```plaintext google.com, pub-, DIRECT, f08c47fec0942fa0 ``` 3. Replace `` with your **AdSense Publisher ID**. ::callout{icon="i-heroicons-light-bulb"} **Why use `ads.txt`?** It helps **prevent ad fraud** and ensures that **only your site** can display your ads. :: ## Enabling Auto Ads Auto Ads allow Google to **automatically** place ads for **better optimization**. ::tabs :::div{label="Example" icon="i-heroicons-code-bracket-square"} ```ts [nuxt.config.ts] export default defineNuxtConfig({ scripts: { registry: { googleAdsense: { client: "ca-pub-", // AdSense Publisher ID autoAds: true, // Enable Auto Ads }, }, }, }); ``` ::: :::div{label="Output" icon="i-heroicons-magnifying-glass-circle"} ```html ``` ::: :: ## Using `ScriptGoogleAdsense` Component It provides a simple way to **embed ads** in your Nuxt app. ```vue ``` ### Component Props | Prop | Description | | ---------------------------- | --------------------------------------------------------------------------------------- | | `data-ad-client` | Your **Google Adsense Publisher ID**(`ca-pub-XXXXXXXXXX`). | | `data-ad-slot` | Your **Ad Slot ID** (available in AdSense dashboard). | | `data-ad-format` | Ad format type (`auto`, `rectangle`, `horizontal`, `vertical`, `fluid`, `autorelaxed`). | | `data-ad-layout` | Layout (`in-article`, `in-feed`, `fixed`). | | `data-full-width-responsive` | **Set to `true`** to make the ad responsive. | #### Example Usage with `data-ad-layout` To specify a layout for your ads (such as "in-article"), you can use the `data-ad-layout` attribute: ```vue ``` ## How to Handle Ad-Blockers? If a user has an **ad-blocker enabled**, you can show **fallback content**. ```vue ``` ## Using `useScriptGoogleAdsense` Composable The `useScriptGoogleAdsense` composable allows **fine-grain control** over the AdSense script. ```ts export function useScriptGoogleAdsense( _options?: GoogleAdsenseInput ) {} ``` See the [Registry Scripts Guide](/docs/guides/registry-scripts) for advanced usage. ## GoogleAdsenseApi Interface This interface defines the structure of the Google Adsense API for better TypeScript support. ```ts export interface GoogleAdsenseApi { adsbygoogle: any[] & { loaded: boolean }; } ``` ## GoogleAdsenseInput You can define the input options for the `useScriptGoogleAdsense` composable using the following structure: ```ts export const GoogleAdsenseOptions = object({ /** * The Google Adsense Publisher ID. */ client: optional(string()), /** * Enable or disable Auto Ads. */ autoAds: optional(boolean()), }); ``` ::callout{icon="i-heroicons-light-bulb" to="https://support.google.com/adsense" target="_blank"} Need more help? Check out the official **Google AdSense Guide** :: --- - **Page:** useScript() - **Source:** https://scripts.nuxt.com/docs/api/use-script - **Description:** Load and control third-party scripts with Nuxt-specific options. ::callout{icon="i-heroicons-play" to="https://stackblitz.com/github/nuxt/scripts/tree/main/examples/custom-script" target="_blank"} Open the [live Custom Script example](https://stackblitz.com/github/nuxt/scripts/tree/main/examples/custom-script). :: This composable wraps Unhead's [`useScript()`{lang="ts"}](https://unhead.unjs.io/docs/head/guides/core-concepts/loading-scripts){lang="ts"}, adding Nuxt triggers, bundling, proxying, and `reload()`{lang="ts"}. It also has an experimental Partytown path with a smaller API, described below. ## Signature ```ts export function useScript = Record>( input: UseScriptInput, options?: NuxtUseScriptOptions, ): UseScriptContext, T>> ``` ## Arguments ### `UseScriptInput` Pass a URL string or an object containing script tag attributes. ```ts export type UseScriptInput = string | { src: string async?: boolean defer?: boolean type?: string integrity?: string crossorigin?: string innerHTML?: string textContent?: string referrerpolicy?: string // Additional Unhead script and data attributes are supported. } ``` Unhead's [complete script example](https://unhead.unjs.io/docs/head/guides/core-concepts/loading-scripts/#complete-example) shows tag attributes alongside loading options. ### `NuxtUseScriptOptions` Nuxt Scripts extends Unhead's [script triggers and warmup options](https://unhead.unjs.io/docs/head/guides/core-concepts/loading-scripts/#how-do-i-control-when-scripts-load) with these options: - `resolve` - Resolve the loaded SDK with lifecycle-bound `signal` and `waitFor` helpers. - `use` - Legacy synchronous or async SDK resolver. Prefer `resolve` for callback-based readiness. - `trigger` - [Triggering Script Loading](/docs/guides/script-triggers) - `bundle` - Control [first-party bundling](/docs/guides/first-party). - `proxy` - Enable or disable supported collection proxying for a registry script. - `partytown` - Run a supported registry script through Partytown. ::callout{color="amber"} `partytown: true` takes an early SSR path. It writes a ` ``` ## Generated plugin The module turns `scripts.globals` into a virtual Nuxt plugin. That plugin calls [`useScript()`{lang="ts"}](/docs/api/use-script){lang="ts"}, so globals share its defaults and singleton behavior. ::code-group ```ts [nuxt.config.ts] export default defineNuxtConfig({ scripts: { globals: { tracker: 'https://analytics.com/tracker.js', } } }) ``` ```vue [components/Tracking.vue] ``` :: --- - **Page:** CORS and Security Attributes - **Source:** https://scripts.nuxt.com/docs/guides/cors - **Description:** Configure cross-origin credentials, referrer policy, and integrity checks for scripts. ## Background When loading scripts from external domains, the script element's [`crossorigin` setting](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Attributes/crossorigin) determines whether the browser uses Cross-Origin Resource Sharing (CORS). For third-party scripts, this affects: - Whether the browser sends cookies with requests - Access to error details for debugging - Subresource Integrity (SRI) validation ## Default Behavior Nuxt Scripts applies privacy-focused defaults to cross-origin scripts: ```html ``` The attributes have separate effects: - **`crossorigin="anonymous"`**: Prevents the script request from sending cross-origin credentials, including cookies - **[`referrerpolicy="no-referrer"`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Referrer-Policy)**: Prevents sharing the page URL with third-party servers Some vendor scripts require cookies or referrer information, so these defaults are not compatible with every endpoint. ## Common CORS Errors ### Script Fails to Load ```text Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource ``` This occurs when a server doesn't return an appropriate `Access-Control-Allow-Origin` header but `crossorigin="anonymous"` is set. Some third-party scripts don't support CORS. ### Script Loads but Functions Fail The script loads but functionality is broken because it expected cookies or session data. ### Error Details Hidden ```js window.onerror = msg => console.log(msg) // Shows: "Script error." instead of actual error ``` Without `crossorigin`, browsers hide error details from external scripts for security. ## Configuring CORS Attributes ### Per-Script Configuration Disable CORS attributes for scripts that don't support them: ```ts useScript({ src: 'https://example.com/script.js', crossorigin: false, // Remove crossorigin attribute referrerpolicy: false, // Remove referrerpolicy attribute }) ``` Or use a different `crossorigin` value: ```ts useScript({ src: 'https://example.com/script.js', crossorigin: 'use-credentials', // Send cookies with request }) ``` The `crossorigin` and `referrerpolicy` fields are script input attributes, so configure them per script. They are not part of `defaultScriptOptions`. ## Crossorigin Values | Value | Cross-origin credentials | Error details | Use case | | ----------------- | ----------------------------------------------------- | -------------------------------------------------------------------- | -------------------------------- | | `anonymous` | Not sent | Available if the server allows the origin | Privacy-focused default | | `use-credentials` | The browser sends them if its policy allows | Available if the server allows credentials and the requesting origin | Scripts requiring authentication | | `false` | Browser cookie policy determines whether to send them | Hidden | Scripts without CORS support | ## Registry Scripts Some registry definitions disable `crossorigin` for compatibility with their vendors: ```ts const config = { scriptInput: { src: 'https://js.stripe.com/basil/stripe.js', crossorigin: false, referrerpolicy: false, } } ``` Scripts with `crossorigin: false` include: - Stripe - YouTube Player - Usercentrics - Ahrefs Web Analytics - Google Sign-In - Google reCAPTCHA - Meta Pixel - TikTok Pixel - X (Twitter) Pixel - Snapchat Pixel - LinkedIn Insight Tag - Cloudflare Web Analytics - Lemon Squeezy - Calendly - Matomo Analytics - Bing UET If a registry script fails at the network layer, inspect these attributes alongside the response's CORS headers. ## Subresource Integrity For cross-origin resources, [Subresource Integrity requires a CORS check](https://developer.mozilla.org/en-US/docs/Web/Security/Defenses/Subresource_Integrity). When using [bundled scripts with SRI](/docs/api/nuxt-config#assetsintegrity), Nuxt adds `crossorigin="anonymous"` automatically: ```ts [nuxt.config.ts] export default defineNuxtConfig({ scripts: { assets: { integrity: true, // Automatically sets crossorigin="anonymous" } } }) ``` ## Troubleshooting ### Script Won't Load 1. Check the browser console for CORS errors 2. Set `crossorigin: false` to disable CORS mode 3. Verify the third-party server supports CORS headers ### Script Loads but Broken 1. The script may require cookies; try `crossorigin: 'use-credentials'` 2. The script may need the referrer; set `referrerpolicy: false` 3. Check if the script expects you to load it without CORS attributes ### Debugging External Script Errors To see full error messages from external scripts: 1. Ensure the script has `crossorigin="anonymous"` 2. Verify that the server returns an appropriate `Access-Control-Allow-Origin` header 3. If the server doesn't support CORS, you won't get detailed errors ### Bundling as an alternative [Bundling the script](/docs/guides/first-party) moves the initial request to your own domain, where it no longer needs cross-origin script handling. --- - **Page:** First-Party Mode: Privacy-Focused Proxy - **Source:** https://scripts.nuxt.com/docs/guides/first-party - **Description:** Route third-party script traffic through a reverse proxy on your domain with automatic anonymization. ## Why Proxy Third-Party Scripts? Third-party requests connect your visitors' browsers directly to vendor servers, exposing IP addresses and browser signals that can contribute to a [fingerprint](https://developer.mozilla.org/en-US/docs/Glossary/Fingerprinting). The current [Nuxt Scripts audit snapshot](https://github.com/nuxt/scripts/blob/main/packages/script/src/script-sizes.json) shows why counting script tags is not enough: integrations can set several cookies, contact multiple domains, and inspect device data after their loader runs. Ad blockers can stop these requests, leaving gaps in analytics that depend on them. ## How First-Party Mode Works For supported registry scripts, first-party mode can bundle the SDK, proxy its runtime requests, and anonymize data before forwarding it. Each registry definition declares which of these capabilities the script supports. ### Bundling During `nuxt build`, the module downloads third-party scripts and saves them as local assets at `/_scripts/assets/[hash].js`. At runtime, your server (or CDN) serves these files from your own domain instead of the original third-party CDN. The initial request no longer reaches the original host or pays its [DNS and connection setup costs](https://developer.mozilla.org/en-US/docs/Web/Performance/Guides/dns-prefetch). Content-addressed filenames also let browsers cache bundled scripts long term. ### Reverse Proxy Runtime requests (analytics beacons, pixel fires, tracking calls) are intercepted and forwarded through Nitro server routes at `/_scripts/p/`. The module routes requests through source rewriting, runtime wrappers, or SDK endpoint configuration: 1. **AST rewriting** at build time: third-party domains in the bundled script source are replaced with your proxy path 2. **Runtime wrappers** on the client: bundled SDKs use Nuxt Scripts wrappers for dynamically constructed URLs. 3. **SDK endpoint configuration**: Google Analytics and PostHog receive a collection endpoint on your server. Their SDKs send events there without source rewriting. Supported script and collection requests use same-origin URLs, so cookies set on those responses are not [third-party cookies](https://developer.mozilla.org/en-US/docs/Web/Privacy/Guides/Third-party_cookies). Host-based blocking rules are less likely to match them, although path-based or behavioral rules can still block requests. ### Anonymization By default, the proxy anonymizes IP addresses to subnet level and removes sensitive headers such as cookies and auth tokens before forwarding a request. Each script's privacy tier determines whether it also changes the user agent, screen dimensions, and hardware fingerprints. Even with minimal anonymization, requests routed through the reverse proxy appear same-origin and do not expose the browser's direct connection to the upstream service. ::callout{color="amber"} The privacy transformer deliberately preserves analytics identifiers and user data fields such as `uid`, `cid`, `email`, and `phone`; SDKs may hash some of those values before sending them. First-party mode reduces selected network and fingerprinting data. It does not make an identified analytics payload anonymous. :: ## Usage Registry scripts use their supported first-party capabilities by default. Adding an entry prepares its proxy routes, bundled assets, types, and composables without loading the script: ```ts [nuxt.config.ts] export default defineNuxtConfig({ scripts: { registry: { // Infrastructure only; use composables to load on specific pages googleAnalytics: { id: 'G-XXXXXX' }, metaPixel: { id: '123456' }, // Infrastructure + global auto-load plausibleAnalytics: { scriptId: 'YOUR_SCRIPT_ID', trigger: 'onNuxtReady' }, } } }) ``` Scripts without `trigger` are infrastructure only: the module prepares any supported infrastructure for that script (proxy routes, bundling, composables), but the script only loads when you call the composable in a component. Add `trigger` to auto-load globally. ## Privacy Tiers Each proxied script has a default privacy tier chosen around the data its SDK needs: | Tier | What's anonymized | Scripts | | ---------------- | ---------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Full** | IP, user agent, language, screen, timezone, hardware fingerprints | Meta Pixel, X Pixel, Snapchat Pixel, Reddit Pixel, LinkedIn Insight Tag | | **Heatmap-safe** | IP, language, hardware fingerprints (preserves screen and user agent for session replay) | Google Analytics, Google AdSense, Microsoft Clarity, Hotjar | | **IP only** | IP addresses anonymized to subnet level | Plausible, PostHog, Umami, Cloudflare Web Analytics, Vercel Analytics, Rybbit, Databuddy, Matomo, Ahrefs Web Analytics, Intercom, YouTube, Vimeo, Gravatar, Calendly | Sensitive headers (`cookie`, `authorization`) are **always** stripped regardless of tier. ### Six Privacy Flags Each tier selects from six privacy flags. The [privacy transformer source](https://github.com/nuxt/scripts/blob/main/packages/script/src/runtime/server/utils/privacy.ts) defines the exact header, query, and payload fields each flag changes: | Flag | What it does | | ----------- | ------------------------------------------------------------------------------------------------------------------ | | `ip` | Anonymizes IP addresses to subnet level in headers and payload params | | `userAgent` | Normalizes User-Agent to browser family + major version (e.g. `Mozilla/5.0 (compatible; Chrome/131.0)`{lang="ts"}) | | `language` | Normalizes Accept-Language to primary language tag | | `screen` | Generalizes screen resolution, viewport, hardware concurrency, and device memory to common buckets | | `timezone` | Generalizes timezone offset and IANA timezone names | | `hardware` | Anonymizes canvas/webgl/audio fingerprints, plugin/font lists, browser versions, and device info | ### Tier Flag Matrix | Flag | IP Only | Heatmap-safe | Full | | ----------- | :-----------------------------------: | :-----------------------------------: | :-----------------------------------: | | `ip` | ::icon{name="i-heroicons-check"} :: | ::icon{name="i-heroicons-check"} :: | ::icon{name="i-heroicons-check"} :: | | `userAgent` | | | ::icon{name="i-heroicons-check"} :: | | `language` | | ::icon{name="i-heroicons-check"} :: | ::icon{name="i-heroicons-check"} :: | | `screen` | | | ::icon{name="i-heroicons-check"} :: | | `timezone` | | | ::icon{name="i-heroicons-check"} :: | | `hardware` | | ::icon{name="i-heroicons-check"} :: | ::icon{name="i-heroicons-check"} :: | **IP Only** zeroes the final IPv4 octet (/24) or keeps the first 48 bits of an IPv6 address. This reduces precision but does not guarantee a particular geolocation accuracy. **Heatmap-safe** normalizes language and hardware fingerprint data while preserving user agent and screen dimensions used by session replay tools. **Full** applies all six privacy transforms. ### Global Override Use the top-level `privacy` option to replace the defaults for every proxied script: ```ts [nuxt.config.ts] export default defineNuxtConfig({ scripts: { privacy: true, // Full anonymization for all proxied scripts } }) ``` Or selectively override specific flags: ```ts [nuxt.config.ts] export default defineNuxtConfig({ scripts: { privacy: { ip: true }, // Anonymize IP on proxied traffic; direct requests are unaffected } }) ``` ### Per-Script Privacy Override Add `privacy` to a registry entry to override one script: ```ts [nuxt.config.ts] export default defineNuxtConfig({ scripts: { registry: { // Apply all privacy transforms to Plausible instead of its IP-only default plausibleAnalytics: { scriptId: 'YOUR_SCRIPT_ID', privacy: true }, // IP-only for self-hosted PostHog where you control the data posthog: { apiKey: 'phc_xxx', privacy: { ip: true } }, } } }) ``` ### Disabling Anonymization Set `privacy: false` per script or globally to stop anonymizing data. Requests still pass through your server: ```ts [nuxt.config.ts] export default defineNuxtConfig({ scripts: { privacy: false, // No anonymization for any script (routing still active) } }) ``` ## Hiding Hostnames By default, a proxy path includes the third-party hostname, for example `/_scripts/p/us.i.posthog.com/e/`. With a self-hosted service, that can expose an internal domain such as `/_scripts/p/analytics.internal.example.com/api/send`. A verbatim hostname also makes the request easier for ad blockers and network observers to classify. Use `proxy.alias` to replace the hostname segment with an alias. Set `alias: true` to auto-generate a short opaque alias per domain: ```ts [nuxt.config.ts] export default defineNuxtConfig({ scripts: { proxy: { alias: true, // /_scripts/p/a1b2c3d4/e/ } } }) ``` Or map specific domains to custom aliases. Domains not listed keep their verbatim hostname: ```ts [nuxt.config.ts] export default defineNuxtConfig({ scripts: { proxy: { alias: { 'us.i.posthog.com': 'ph', 'analytics.internal.example.com': 'a', } } } }) ``` Aliases apply everywhere a proxy path is produced: build-time URL rewrites, auto-injected endpoints (such as PostHog's `apiHost`), runtime-intercepted requests, and Partytown worker requests. The server handler resolves the alias back to the real domain before forwarding upstream. ::callout{type="info"} Aliases change only the hostname segment. Set the top-level `prefix` option, for example `prefix: '/_t'`, to change `/_scripts/p`. :: ::callout{type="warning"} Aliases keep the real hostname out of request **URLs**. If an SDK builds its collection URL at runtime, the host may still appear in the client JavaScript. Aliasing changes the network-visible path; it does not obfuscate your bundle. :: ## Opting Out ### Per-Script Disable proxying for a specific script using `proxy: false` in its registry config: ```ts [nuxt.config.ts] export default defineNuxtConfig({ scripts: { registry: { plausibleAnalytics: { scriptId: 'YOUR_SCRIPT_ID', proxy: false }, googleAnalytics: { id: 'G-XXXXXX' }, // still proxied } } }) ``` For scripts whose collection requests depend on AST URL rewriting, setting `bundle: false` also prevents those requests from using the proxy. Without a bundled script source, there are no SDK URLs or calls for the transform to rewrite. ```ts [nuxt.config.ts] export default defineNuxtConfig({ scripts: { registry: { // Disables bundling and the proxy rewrites that depend on it metaPixel: { id: '123456', bundle: false }, } } }) ``` ::callout{type="info"} PostHog receives its proxy endpoint through SDK config. Google Analytics receives its collection endpoint through SDK configuration. Both can proxy without bundling. Matomo does not: its normal main-thread loader remains direct unless you use the separate Partytown routing path described below. :: ### Static Hosting (SSG) The reverse proxy requires a **server runtime**. A fully static deployment serves the output of [`nuxt generate`](https://nuxt.com/docs/getting-started/prerendering) without a Nitro process to handle `/_scripts/p/**`. Nuxt Scripts detects static output (`nuxt generate`, `nuxt build --prerender`, or a static Nitro preset) and disables proxying automatically, with a build warning listing the affected scripts. Scripts still bundle and load from your domain, but their collection requests keep their original third-party URLs and go directly to their origins. Proxy privacy and anonymization do not apply to those direct requests. To keep requests proxied and anonymized, deploy the server output of `nuxt build` to a host that runs Nitro. ## Proxy Endpoint Security Embed, avatar, and analytics proxy routes are public resources. They do not contain server-side API keys. Each route accepts only the upstream hosts and request shapes declared by its integration. ::callout{type="warning"} Runtime proxy fetches validate the initial upstream URL and every redirect target before requesting it. Direct local, private, link-local, and reserved targets are rejected on every runtime; Node deployments also validate and pin DNS results before opening the socket. Image routes reject active content types such as HTML and SVG. The Instagram embed route restricts post and stylesheet hosts, then sanitizes the returned fragment before client rendering. :: Nuxt Scripts does not proxy Google Maps requests. Static Maps loads from Google with the public browser key, while location lookup uses the Maps JavaScript Places service. Apply website and API restrictions to the key, then configure Google Cloud quotas to cap spend. See [Google Maps Platform security guidance](https://developers.google.com/maps/api-security-best-practices). If public embed traffic needs request limits, configure them at your deployment edge or add application middleware. Nitro 2 route rules do not provide a portable rate limiter. ## Supported Scripts The groups below follow the [capability declarations in the Nuxt Scripts registry](https://github.com/nuxt/scripts/blob/main/packages/script/src/registry.ts). ### Full First-Party (Bundled + Proxied) These scripts are downloaded at build time, served from your domain, and have their collection requests proxied through your server: | Category | Scripts | | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | **Analytics** | [Plausible](/scripts/plausible-analytics), [Cloudflare Web Analytics](/scripts/cloudflare-web-analytics), [Umami](/scripts/umami-analytics), [Rybbit](/scripts/rybbit-analytics), [Databuddy](/scripts/databuddy-analytics), [Ahrefs Web Analytics](/scripts/ahrefs-analytics), [Vercel Analytics](/scripts/vercel-analytics), [Microsoft Clarity](/scripts/clarity), [Hotjar](/scripts/hotjar) | | **Ad Pixels** | [Meta Pixel](/scripts/meta-pixel), [X Pixel](/scripts/x-pixel), [Snapchat Pixel](/scripts/snapchat-pixel), [Reddit Pixel](/scripts/reddit-pixel), [LinkedIn Insight Tag](/scripts/linkedin-insight), [Google AdSense](/scripts/google-adsense) | | **Video** | [Vimeo Player](/scripts/vimeo-player) | | **Utility** | [Intercom](/scripts/intercom), [Gravatar](/scripts/gravatar) | ### Proxy Only These integrations keep their SDK outside the build bundle and can send collection requests through your server: | Script | How it works | | --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [Google Analytics](/scripts/google-analytics) | Visitor-specific loader stays remote. Collection requests use the proxy on the main thread and with Partytown. | | [PostHog](/scripts/posthog) | SDK installed as [`posthog-js`](https://posthog.com/docs/libraries/js#option-2-install-via-package-manager). Proxy endpoint auto-injected via `apiHost` config. | Google still receives direct loader and other SDK requests from the browser. Proxy privacy transforms apply only to collection requests. ### Bundled, with Third-Party Runtime Traffic These integrations serve their main loader from your domain, but some runtime requests still go directly to third-party servers: | Script | Remaining direct traffic | | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | [Fathom](/scripts/fathom-analytics) | Fathom's bot detection rejects beacons from the server's IP, so Nuxt Scripts bundles the SDK but leaves beacons direct. | | [Pulse](/scripts/pulse-analytics) | Pulse identifies visitors from the connecting IP, so proxied beacons would merge everyone into one visitor. Nuxt Scripts bundles the tracker but leaves beacons direct. | | [Statable](/scripts/statable-analytics) | Statable identifies visitors from the connecting IP and user agent, so proxied beacons would merge everyone into one visitor. Nuxt Scripts bundles the tracker but leaves beacons direct. | | [Segment](/scripts/segment) | SDK constructs API URLs dynamically, bypassing request interception. | | [Crisp](/scripts/crisp) | SDK loads secondary scripts and CSS at runtime from `client.crisp.chat`. | | [Mixpanel](/scripts/mixpanel-analytics) | No proxy integration yet. | | [Bing UET](/scripts/bing-uet) | No proxy integration yet. | | [npm](/scripts/npm) | The registry entry bundles the selected package file but has no vendor-specific proxy rules. | | [YouTube Player](/scripts/youtube-player) | Nuxt Scripts bundles the API loader, but the facade supplies a direct `youtube.com` or `youtube-nocookie.com` iframe host after the bundle transform runs. The iframe and its internal requests remain third-party. | | [Calendly](/scripts/calendly) | The widget script and assets can use first-party routing, but booking iframes and their traffic load directly from `calendly.com`. | Bundling avoids the main loader's connection to its original host, but it does not make all runtime traffic first-party. ### Direct Loading (No Active Bundle or Collection Proxy) These integrations load their SDKs and runtime traffic directly from their configured hosts: | Script | Direct-loading behavior | | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | [TikTok Pixel](/scripts/tiktok-pixel) | Loader and collection requests stay direct to preserve visitor IDs and vendor cookies. Proxy anonymization does not apply. | | [Google Tag Manager](/scripts/google-tag-manager) | Visitor-specific loader and scripts it loads remain direct. | | **Stripe** | [Stripe requires Stripe.js to load directly from `js.stripe.com`](https://docs.stripe.com/payments/finalize-payments-on-the-server?platform=web&type=setup) for PCI compliance. | | **PayPal** | Nuxt Scripts loads PayPal's v6 core from PayPal's documented [`/web-sdk/v6/core` URL](https://developer.paypal.com/upgrade/ec/guide/Web%20SDK%20v6/); the registry declares no bundle or collection proxy. | | **Google reCAPTCHA** | Nuxt Scripts loads `api.js` or `enterprise.js` directly from Google, or from `recaptcha.net`; the registry declares no bundle or collection proxy. Google separately recommends loading reCAPTCHA early because [more page context improves its assessment](https://developers.google.com/recaptcha/docs/loading). | | **Google Sign-In** | Google says [self-hosted and offline copies are unsupported](https://developers.google.com/identity/gsi/web/guides/get-google-api-clientid) so clients receive security and compatibility updates. | | **Matomo** | The main-thread SDK is not bundled, so its normal requests keep using the configured Matomo host. With `partytown: true`, Nuxt Scripts can route matching worker requests through Partytown's `resolveUrl`; the Partytown limitations below still apply. | | **Carbon Ads** | The component loads the ad script directly. Collection rewriting skips this entry because it does not bundle. | | **Lemon Squeezy** | The composable loads the payment widget directly. Collection rewriting skips this entry because it does not bundle. | Google Maps, SpeedCurve LUX, and Usercentrics also load directly because they do not declare bundle or collection-proxy capabilities. ## Partytown (Web Worker) Load individual scripts in a [Partytown web worker](https://partytown.qwik.dev/how-does-partytown-work/) by setting `partytown: true` per script. The `trigger` below causes the registry plugin to call the composable globally; it does not delay the Partytown tag, which is written into the server-rendered HTML. ```ts [nuxt.config.ts] export default defineNuxtConfig({ modules: ['@nuxtjs/partytown', '@nuxt/scripts'], scripts: { registry: { plausibleAnalytics: { scriptId: 'YOUR_SCRIPT_ID', partytown: true, trigger: 'onNuxtReady' }, } } }) ``` Forward arrays are auto-configured for supported scripts. You must install `@nuxtjs/partytown`. ::callout{color="amber"} The current Partytown path keeps only the script `src`. It skips other attributes, registry initialization hooks, trigger timing, and the usual composable proxy/context API. A declared Partytown capability means Nuxt Scripts knows the forwarding and routing configuration; it does not guarantee that an integration which depends on `data-*` attributes or `clientInit` will initialize correctly. Verify the generated tag and vendor traffic. :: ::callout{icon="i-heroicons-exclamation-triangle" color="amber"} The Partytown tracker has an [open report of dropped GA4 sessions](https://github.com/QwikDev/partytown/issues/583) when Google Tag Manager runs in a worker. Nuxt Scripts does not declare Partytown support for GTM. Test analytics delivery before moving GA4 to Partytown. :: ## Consent Integration First-party mode controls *where* requests go. [Consent triggers](/docs/guides/consent) control *when* main-thread scripts load. The current Partytown path ignores trigger timing, so do not rely on a Nuxt Scripts consent trigger to gate a Partytown tag. ```vue ``` ### Third-Party Consent Managers For tools like OneTrust, CookieBot, or Osano, bind their consent signal to a reactive ref: ```vue ``` Or keep the registry entry infrastructure-only and call `load()`{lang="ts"} on the composable result when consent is granted: ```ts [nuxt.config.ts] export default defineNuxtConfig({ scripts: { registry: { // Infrastructure only, load manually after consent googleAnalytics: { id: 'G-XXXXXX' }, } } }) ``` ```vue [app.vue] ``` The [Consent Management guide](/docs/guides/consent) covers `useScriptTriggerConsent()`{lang="ts"} and vendor-specific controls. ::callout{type="info"} Routing a request through your domain does not settle the consent question. For EU deployments, the EDPB's final [Article 5(3) technical-scope guidance](https://www.edpb.europa.eu/system/files/documents/2024-10/edpb_guidelines_202302_technical_scope_art_53_eprivacydirective_v2_en_0.pdf) covers tracking URLs, pixels, and JavaScript as well as cookies. Assess each script and jurisdiction before loading it. :: ## Troubleshooting | Problem | Fix | | ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Analytics not tracking | Check DevTools โ†’ Network for `/_scripts/p/` requests. Check Nitro server logs for proxy errors | | Proxy not working on static site | Static output disables the proxy automatically and collection requests go direct. Deploy the `nuxt build` server output to enable proxying. See [Static Hosting](#static-hosting-ssg) | | Stale script | Remove `node_modules/.cache/nuxt/scripts` and rebuild | | Build download fails | Set `assets.fallbackOnSrcOnBundleFail: true`{lang="ts"} to fall back to direct loading | | Debugging | Open Nuxt DevTools โ†’ Scripts to see proxy routes and privacy status | | Geo accuracy reduced | IP anonymization uses an IPv4 /24 or IPv6 /48 prefix. Set `privacy: false` per script or globally to forward exact IPs | | `bundle: false` stopped collection proxying | Keep bundling enabled for scripts whose proxy support depends on AST rewriting. Auto-injected integrations such as PostHog do not require bundling; main-thread Matomo remains direct | | Per-script opt-out not working | For scripts with auto-inject (Plausible, PostHog, Umami, Rybbit, Databuddy), use `proxy: false` in the registry config | --- - **Page:** SpeedCurve LUX - **Source:** https://scripts.nuxt.com/scripts/speedcurve - **Description:** Use SpeedCurve LUX Real User Monitoring in your Nuxt app to measure performance experienced by real users, with automatic SPA navigation tracking. [SpeedCurve LUX](https://www.speedcurve.com/features/performance-monitoring/) collects field performance data, including Core Web Vitals, custom timing marks, and JavaScript errors. ::script-stats :: ::script-docs :: By default, the composable injects the LUX primer into ``{lang="html"} immediately and loads `lux.js` during Nuxt hydration. ## Setup SpeedCurve LUX is opt-in. Register it in `scripts.registry.speedcurve` to resolve and inline the LUX primer at build time. Install the `@speedcurve/lux` peer dep alongside: ```bash pnpm add -D @speedcurve/lux ``` ```ts [nuxt.config.ts: composable-only (no global load)] export default defineNuxtConfig({ modules: ['@nuxt/scripts'], scripts: { registry: { // Minimum registration: enables the composable per page. // Pass `id` here and you can omit it from each useScriptSpeedCurve() call. speedcurve: {}, }, }, }) ``` ```ts [nuxt.config.ts: auto-load globally] export default defineNuxtConfig({ modules: ['@nuxt/scripts'], scripts: { registry: { speedcurve: { id: 'YOUR_SPEEDCURVE_ID', trigger: 'client' }, }, }, }) ``` For SpeedCurve, the registry `trigger` above is the marker that generates a global composable call. `useScriptSpeedCurve()`{lang="ts"} hard-codes the effective trigger to `client`, so `onNuxtReady`, manual, consent, and custom promise triggers in registry or composable options do not change its load timing. If `speedcurve` isn't registered, `useScriptSpeedCurve` builds with an empty primer fallback. If it's registered but `@speedcurve/lux` is missing, the build fails with an install hint. Pinning your own `@speedcurve/lux` version means you control when the primer snippet updates. You can access the `LUX` object as a proxy directly, or await `$script` to get the loaded instance. ::code-group ```ts [Proxy] const { proxy } = useScriptSpeedCurve({ id: 'YOUR_ID' }) proxy.LUX.label = 'my-page' ``` ```ts [onLoaded] const { onLoaded } = useScriptSpeedCurve({ id: 'YOUR_ID' }) onLoaded(({ LUX }) => { LUX.label = 'my-page' }) ``` :: ## SPA navigation Set `spaMode: true` to enable SpeedCurve's [SPA tracking mode](https://support.speedcurve.com/docs/single-page-applications). The composable wires Vue Router automatically: - `router.beforeEach` calls `LUX.startSoftNavigation()`{lang="ts"}, closing the previous beacon and starting a new one - `nuxt.hook('page:finish')`{lang="ts"} calls `LUX.markLoadTime()`{lang="ts"} after the next paint to set the END mark - Canceled navigations tag the phantom beacon with `addData('luxNavFailed', '1')`{lang="ts"} Install this in `app.vue`. Nuxt Scripts creates the automatic router hooks once per browser session, so the first auto-tracked call supplies their label and navigation options. ```ts [app.vue] useScriptSpeedCurve({ id: 'YOUR_ID', spaMode: true, autoTrackSpaNavigations: true, // default when spaMode is true }) ``` To disable auto-wiring and instrument manually: ```ts useScriptSpeedCurve({ id: 'YOUR_ID', spaMode: true, autoTrackSpaNavigations: false, }) // Then call LUX.startSoftNavigation() and LUX.markLoadTime() yourself ``` ## Custom page labels By default the composable uses `String(to.name ?? to.path)`{lang="ts"} as the page label for each navigation. Pass a function to `label` to override it: ```ts useScriptSpeedCurve({ id: 'YOUR_ID', spaMode: true, label: to => to.meta.title as string ?? to.path, }) ``` Set `label: false` to disable labeling entirely. Pass a plain string to set a static label (only meaningful without `spaMode`, since the router hook overwrites it on every navigation). ## CSP Add these directives to your Content Security Policy: ```text script-src cdn.speedcurve.com; img-src lux.speedcurve.com; connect-src lux.speedcurve.com beacon.speedcurve.com; ``` See SpeedCurve's [LUX Content Security Policy guide](https://support.speedcurve.com/docs/add-rum-to-your-csp) for the vendor's current directives. ::script-types :: ## Example Load SpeedCurve LUX through `app.vue` with SPA tracking enabled: ```vue [app.vue] ``` --- - **Page:** Umami Analytics - **Source:** https://scripts.nuxt.com/scripts/umami-analytics - **Description:** Load Umami, identify sessions, and inspect or filter event payloads before they are sent. [Umami](https://umami.is/) is an open-source web analytics platform that can run in Umami Cloud or on your own server. ::script-stats :: ::script-docs :: ### Self-hosted Umami If you use a self-hosted version of Umami, set `hostUrl` to your Umami origin. This maps to Umami's documented [`data-host-url`](https://docs.umami.is/docs/tracker-configuration#data-host-url) setting and tells the tracker where to send events. ```ts useScriptUmamiAnalytics({ websiteId: 'YOUR_WEBSITE_ID', hostUrl: 'https://my-self-hosted' }) ``` Use `scriptInput.src` only if you also need to override the script URL itself. ## Identify Sessions and Filter Data ### Identify a Session Umami's [`identify`](https://docs.umami.is/docs/tracker-functions#sessions) function accepts either a distinct ID or an object with session data: ```ts const { proxy } = useScriptUmamiAnalytics({ websiteId: 'YOUR_WEBSITE_ID' }) // Set a distinct ID proxy.identify('user-12345') // Attach data to the session proxy.identify({ userId: 'user-12345', plan: 'premium' }) ``` ### Filter Data with `beforeSend` Use [`beforeSend`](https://docs.umami.is/docs/tracker-configuration#data-before-send) to inspect, modify, or cancel a payload before Umami receives it: ```ts useScriptUmamiAnalytics({ websiteId: 'YOUR_WEBSITE_ID', beforeSend: (type, payload) => { // Log what's being sent while debugging console.log('Sending to Umami:', type, payload) // Filter out sensitive data if (payload.url && payload.url.includes('private')) { return false // Cancel send } // Modify payload before sending return { ...payload, referrer: '' // Remove referrer for privacy } } }) ``` You can also provide a string with the name of a globally defined function: ```ts // Define function globally window.myBeforeSendHandler = (type, payload) => { return checkPrivacyRules(payload) ? payload : false } useScriptUmamiAnalytics({ websiteId: 'YOUR_WEBSITE_ID', beforeSend: 'myBeforeSendHandler' }) ``` ::script-types :: --- - **Page:** Carbon Ads - **Source:** https://scripts.nuxt.com/scripts/carbon-ads - **Description:** Show carbon ads in your Nuxt app using a Vue component. [Carbon Ads](https://www.carbonads.net/) serves ads through a standard embed script. Use the headless [``{lang="html"}](/scripts/carbon-ads){lang="html"} component to place one of its ads in a Nuxt page. ::script-stats :: ::script-docs :: ## [``{lang="html"}](/scripts/carbon-ads){lang="html"} Unlike other Nuxt Scripts components, ``{lang="html"} bypasses [`useScript()`{lang="ts"}](/docs/api/use-script){lang="ts"} and inserts the Carbon script into its own `div`. It loads when mounted. Pass an [element event trigger](/docs/guides/script-triggers#element-event-triggers) if the ad should wait for a specific interaction. Carbon's [placement policy](https://www.carbonads.net/placement-policy) requires the ad code to load only once per page and forbids modifying or self-hosting the script. Render one component for the active page. The component requests Carbon's CDN script directly, but it does not enforce the one-component limit. ```vue ``` ### Handling ad blockers Use the `error` slot when Carbon Ads is blocked: ```vue ``` ### Adding UI The component has no inherited styles. This example uses the styles from [nuxt.com](http://nuxt.com): ```vue ``` ### Component API See the [Facade Component API](/docs/guides/facade-components#facade-components-api) for full props, events, and slots. The component's `ready` event receives the injected `HTMLScriptElement`. ::script-types :: --- - **Page:** Instagram Embed - **Source:** https://scripts.nuxt.com/scripts/instagram-embed - **Description:** Server-rendered Instagram embeds without direct browser requests to Instagram. [Instagram](https://instagram.com) hosts photo and video posts. [``{lang="html"}](/scripts/instagram-embed){lang="html"} fetches embed HTML through your server and proxies supported media and static assets. Changing the post URL or caption setting after client-side navigation refetches your Nuxt endpoint. ::script-stats :: ::script-docs{embed} :: This registers the required server API routes (`/_scripts/embed/instagram`, `/_scripts/embed/instagram-image`, and `/_scripts/embed/instagram-asset`) that handle fetching embed HTML and proxying images/assets. ## [``{lang="html"}](/scripts/instagram-embed){lang="html"} ### Demo ::code-group :::instagram-embed-demo{label="Output"} ::: ```vue [Basic Usage] ``` ```vue [With Custom Loading/Error States] ``` ```vue [Custom Rendering] ``` :: ### Slot Props The default slot receives: ```ts interface SlotProps { html: string // Processed embed HTML with scripts removed and scoped CSS injected shortcode: string // The post shortcode (e.g., "C3Sk6d2MTjI") postUrl: string // The original post URL } ``` The rewriter removes `script`, `noscript`, and original `style` elements. It fetches linked Instagram stylesheets, scopes their selectors, and injects the resulting CSS into the returned HTML. It does not perform general HTML sanitization. The default source is restricted to Instagram. If you pass `html` through your own renderer or introduce a custom endpoint, sanitize it according to your application's content policy before using `v-html`. ### Named Slots | Slot | Description | | --------- | ----------------------------------------------------------------------------------- | | `default` | Main content, receives `{ html, shortcode, postUrl }`. By default renders the HTML. | | `loading` | Shown while fetching embed HTML | | `error` | Shown if embed fetch fails, receives `{ error }` | ## Supported URL formats - Posts: `https://www.instagram.com/p/ABC123/` - Reels: `https://www.instagram.com/reel/ABC123/` - TV: `https://www.instagram.com/tv/ABC123/` ## How it works 1. **Server-side fetch**: Nuxt fetches the Instagram embed HTML from `{postUrl}/embed/` 2. **Asset proxying**: Images from Instagram's media hosts and assets from `static.cdninstagram.com` are rewritten to proxy through your server 3. **Script removal**: Nuxt removes Instagram's `embed.js` (not needed for static rendering) 4. **Caching**: Nuxt caches responses for 10 minutes at the server level ## Browser privacy The rendered embed loads no Instagram JavaScript, forwards no Meta `Set-Cookie` response, and sends image and asset requests to your Nuxt server. Instagram sees the server's connection rather than the visitor's. Links in the embed can still take the visitor to Instagram if they choose to open them. ## Limitations - Only supports single-image posts (galleries show first image only) - Videos display as static poster images - Some interactive features are not available (likes, comments) - The image endpoint rejects redirects. The static-asset endpoint currently follows them without revalidating the destination host. ::script-types :: --- - **Page:** Google Tag Manager - **Source:** https://scripts.nuxt.com/scripts/google-tag-manager - **Description:** Load a GTM web container and push page or consent events from Nuxt. [Google Tag Manager](https://marketingplatform.google.com/about/tag-manager/) loads tags from a web container, letting you change tracking configuration without redeploying the app. ::callout GTM can load any tags configured in your container, so its performance cost varies with that configuration. If you only need Google Analytics, the [`useScriptGoogleAnalytics()`{lang="ts"}](/scripts/google-analytics){lang="ts"} composable may be simpler. :: ::callout{icon="i-heroicons-information-circle"} Nuxt Scripts only loads the GTM **container**. Tracking comes from the tags and triggers in your GTM workspace or from your own `dataLayer.push` calls. For automatic page, click, scroll, and video tracking, enable [GA4 Enhanced Measurement](https://support.google.com/analytics/answer/9216061) for the GA4 web data stream. :: ::script-stats :: ::script-docs :: ### Sending page events Use the proxy with [`useScriptEventPage()`{lang="ts"}](/docs/api/use-script-event-page){lang="ts"} to push an event after Nuxt finishes the initial client render or a later route change and updates the page title: ```ts const { proxy } = useScriptGoogleTagManager({ id: 'YOUR_ID' // id is only needed if you haven't configured globally }) useScriptEventPage(({ title, path }) => { // Runs for the initial render when registered early, then for route changes. proxy.dataLayer.push({ event: 'pageview', title, path }) }) ``` ## Consent Mode Google Tag Manager accepts [GCMv2 consent state](https://developers.google.com/tag-platform/security/guides/consent?consentmode=basic). `defaultConsent` enters the queue before the `gtm.js` event; use `consent.update()`{lang="ts"} for later choices. If client state determines the initial default, resolve it before calling the composable and pass it through `defaultConsent`. A later `consent.default()`{lang="ts"} call queues another default after initialization and cannot reproduce the original ordering. ::callout{icon="i-heroicons-play" to="https://stackblitz.com/github/nuxt/scripts/tree/main/examples/cookie-consent" target="_blank"} Open the [cookie consent](https://stackblitz.com/github/nuxt/scripts/tree/main/examples/cookie-consent), [granular consent](https://stackblitz.com/github/nuxt/scripts/tree/main/examples/granular-consent), or [regional consent](https://stackblitz.com/github/nuxt/scripts/tree/main/examples/regional-consent) example on [StackBlitz](https://stackblitz.com). :: ### Consent Mode v2 signals | Signal | Purpose | | -------------------- | -------------------------------- | | `ad_storage` | Cookies for advertising | | `ad_user_data` | Send user data to Google for ads | | `ad_personalization` | Personalized ads (remarketing) | | `analytics_storage` | Cookies for analytics | ### Example ```vue ``` ### Per-region defaults Pass an array to `defaultConsent` to queue one consent-default command per entry, in order. This matches Google's [region-specific consent pattern](https://developers.google.com/tag-platform/security/guides/consent?consentmode=advanced#region-specific-behavior): more specific regions (e.g. `US-CA`) override broader ones (`US`); an entry with no `region` is the unscoped global fallback. ```vue ``` The module forwards each entry verbatim, in input order. Precedence between region-scoped and unscoped defaults is enforced by gtag at runtime, not by ordering. `consent.update()`{lang="ts"} and `consent.default()`{lang="ts"} both accept any `Partial`{lang="ts"}; missing categories stay at their current value. Both methods validate input against the canonical GCMv2 schema and warn via `consola` on unknown keys or non-`granted`/`denied` values. `onBeforeGtmStart` remains available as a general escape hatch for any other pre-`gtm.start` setup (only when the GTM ID is passed directly to the composable, not via `nuxt.config`). ::script-types :: ## Examples ### Server-side GTM With [server-side tagging](https://developers.google.com/tag-platform/tag-manager/server-side/intro), the web container still runs in the browser and sends measurement requests to a server container that you operate. Set each supported tag's transport URL (for example, `server_container_url` in a Google tag) to the server container; changing the GTM loader URL alone does not reroute those requests. Prerequisites include a [server-side GTM container](https://tagmanager.google.com), hosting such as [Cloud Run](https://developers.google.com/tag-platform/tag-manager/server-side/cloud-run-setup-guide) or a [manual deployment](https://developers.google.com/tag-platform/tag-manager/server-side/manual-setup-guide), and a [custom domain](https://developers.google.com/tag-platform/tag-manager/server-side/custom-domain). #### Configuration If your configured first-party tagging domain serves the web container loader, override the source and retain the container ID query parameter: ```ts // nuxt.config.ts export default defineNuxtConfig({ scripts: { registry: { googleTagManager: { id: 'GTM-XXXXXX', trigger: 'onNuxtReady', scriptInput: { src: 'https://analytics.example.com/gtm.js?id=GTM-XXXXXX' } } } } }) ``` This source override only changes where the browser loads `gtm.js`. Configure the web and server containers to route measurement requests to the server container, following Google's [server-side tagging documentation](https://developers.google.com/tag-platform/tag-manager/server-side). --- - **Page:** DeskCrew - **Source:** https://scripts.nuxt.com/scripts/deskcrew - **Description:** Add a lazy-loaded DeskCrew support widget to your Nuxt app. [DeskCrew](https://deskcrew.io/) is a support widget combining live chat, AI answers from your knowledge base, a help centre and a changelog. Use [`useScriptDeskCrew()`{lang="ts"}](#usescriptdeskcrew){lang="ts"} for direct SDK calls, or [``{lang="html"}](#scriptdeskcrew){lang="html"} for a custom chat launcher. ::script-stats :: ::script-docs :: ## [``{lang="html"}](/scripts/deskcrew){lang="html"} The headless facade holds back the DeskCrew widget until its [element trigger](/docs/guides/script-triggers#element-event-triggers) fires. It listens for `click` by default, so a visitor who never opens chat downloads none of the widget. ### Component API See the [Facade Component API](/docs/guides/facade-components#facade-components-api) for full props, events, and slots. #### With environment variables ```ts [nuxt.config.ts] export default defineNuxtConfig({ scripts: { registry: { deskcrew: { trigger: 'onNuxtReady' }, } }, runtimeConfig: { public: { scripts: { deskcrew: { widgetKey: '', // NUXT_PUBLIC_SCRIPTS_DESKCREW_WIDGET_KEY board: '', // NUXT_PUBLIC_SCRIPTS_DESKCREW_BOARD }, }, }, }, }) ``` ```text [.env] NUXT_PUBLIC_SCRIPTS_DESKCREW_WIDGET_KEY= NUXT_PUBLIC_SCRIPTS_DESKCREW_BOARD= ``` ### Events The component emits `ready` once the widget has mounted its launcher, and `error` if the script fails to load. ### Slots `awaitingLoad`, `loading`, `error` and the default slot behave as documented for facade components. ## [`useScriptDeskCrew()`{lang="ts"}](/scripts/deskcrew){lang="ts"} ```ts export function useScriptDeskCrew(_options?: DeskCrewInput) {} ``` ::script-types :: ### Identifying a visitor Identity is a signed token minted by your own backend, so it is a runtime call rather than a `nuxt.config` option. Everything in `nuxt.config` is a deploy-time constant, and baking one visitor's token into a build would hand that identity to every other visitor. ```vue ``` ### Other surfaces `embed()`{lang="ts"}, `changelog()`{lang="ts"} and `surveys()`{lang="ts"} each mount a surface. Call each at most once per page: a second call logs a warning and does nothing. ::callout DeskCrew serves its widget from its own origin and derives its API endpoint from the script's `src`, so this script does not support [bundling](/docs/guides/bundling) or [first-party mode](/docs/guides/first-party). It loads directly from `deskcrew.io`. :: --- - **Page:** Crisp - **Source:** https://scripts.nuxt.com/scripts/crisp - **Description:** Add a lazy-loaded Crisp chat launcher to your Nuxt app. [Crisp](https://crisp.chat/) combines live chat, email, and other customer-messaging channels. Use [`useScriptCrisp()`{lang="ts"}](#usescriptcrisp){lang="ts"} for direct SDK calls, or [``{lang="html"}](#scriptcrisp){lang="html"} for a custom chat launcher. ::script-stats :: ::script-docs :: ## [``{lang="html"}](/scripts/crisp){lang="html"} The headless facade holds back the Crisp SDK until its [element trigger](/docs/guides/script-triggers#element-event-triggers) fires. It listens for `click` by default. ### Demo ::code-group :::crisp-demo{label="Output"} ::: ```vue [Input] ``` :: ### Component API See the [Facade Component API](/docs/guides/facade-components#facade-components-api) for full props, events, and slots. #### With environment variables You can configure the Crisp website ID with an environment variable. ```ts [nuxt.config.ts] export default defineNuxtConfig({ scripts: { registry: { crisp: { trigger: 'onNuxtReady' }, } }, // Public runtime config receives the environment variable. runtimeConfig: { public: { scripts: { crisp: { id: '', // NUXT_PUBLIC_SCRIPTS_CRISP_ID }, }, }, }, }) ``` ```text [.env] NUXT_PUBLIC_SCRIPTS_CRISP_ID= ``` ### Events The [``{lang="html"}](/scripts/crisp){lang="html"} component emits `ready` after it mounts the chatbox and `error` if the script fails to load. ::callout{color="amber"} The current template checks `status === 'loading' || !isReady` before its `error` branch, so the named `#error` slot is unreachable. Listen for `@error` and render fallback UI outside the component until that ordering bug is fixed. :: ```ts const emits = defineEmits<{ ready: [crisp: ReturnType] error: [] }>() ``` ```vue ``` ### Slots **awaitingLoad** This slot displays content while Crisp waits for its configured trigger. ```vue ``` **loading** This slot displays content while Crisp is loading. `ScriptLoadingIndicator` provides the module's standard accessible loading state. ```vue ``` ## [`useScriptCrisp()`{lang="ts"}](/scripts/crisp){lang="ts"} Use [`useScriptCrisp()`{lang="ts"}](/scripts/crisp){lang="ts"} when you need to call Crisp without the launcher component. ```ts export function useScriptCrisp(_options?: CrispInput) {} ``` See [Registry Scripts](/docs/guides/registry-scripts) for trigger and loading options, and the [Crisp API documentation](https://docs.crisp.chat/guides/chatbox-sdks/web-sdk/dollar-crisp/) for available commands. ::script-types :: ## Example Call Crisp through the proxy: ```vue ``` --- - **Page:** Tawk.to - **Source:** https://scripts.nuxt.com/scripts/tawk-to - **Description:** Load the Tawk.to live chat widget and drive it through a typed proxy, reactive state, and event listeners. [Tawk.to](https://www.tawk.to/) is a free live chat widget. [`useScriptTawkTo()`{lang="ts"}](/scripts/tawk-to) loads the widget, types the `window.Tawk_API` command surface, and bridges the `window` events the embed script dispatches into reactive state and typed listeners. ::script-stats :: ::script-docs :: Bundling and proxying are both off. Nobody has verified how the embed script resolves its own API origin, or what live-chat polling connections a proxy would end up in front of, so neither capability is declared rather than guessed at. Find `propertyId` and `widgetId` under **Administration Settings โ†’ Channels โ†’ Chat Widget** in your [Tawk.to](http://Tawk.to) dashboard. ::code-group ```ts [Proxy] const { proxy } = useScriptTawkTo({ propertyId: 'YOUR_PROPERTY_ID', widgetId: 'YOUR_WIDGET_ID', }) function openChat() { proxy.maximize() } ``` ```ts [onLoaded] const { onLoaded } = useScriptTawkTo({ propertyId: 'YOUR_PROPERTY_ID', widgetId: 'YOUR_WIDGET_ID', }) onLoaded((Tawk_API) => { Tawk_API.maximize() }) ``` :: ## Reactive state and events Tawk's embed script dispatches `window` `CustomEvent`s (`tawkLoad`, `tawkStatusChange`, `tawkChatMaximized`, โ€ฆ) alongside its documented `Tawk_API.onXxx = fn` callback-property API. `useScriptTawkTo()`{lang="ts"} bridges those events into five readonly refs and twenty typed listeners, so you don't have to wire `window.addEventListener` yourself: ```vue ``` `chatStatus` is Tawk's own online/away/offline operator status (`getStatus()`{lang="ts"}). It's distinct from `status`, the generic script-load state every registry entry exposes. Every `onXxx` listener returns a teardown function for use with `onScopeDispose`, mirroring the rest of the registry's event-listener helpers. The state refs are a single instance shared by every `useScriptTawkTo()`{lang="ts"} call on the page (there's only ever one Tawk widget), not one instance per call. ## Getters `proxy` is fire-and-forget: calls queue until the script loads and replay once it does, but their return value is always discarded, even after loading. That's fine for actions like `proxy.maximize()`{lang="ts"}, which don't return anything meaningful anyway, but it can't carry a real synchronous getter. `getWindowType`, `getStatus`, `isChatMaximized`, `isChatMinimized`, `isChatHidden`, `isChatOngoing`, `isVisitorEngaged`, and `widgetPosition` are exposed directly on `useScriptTawkTo()`{lang="ts"}'s return value instead, calling straight through to `window.Tawk_API`: ```ts const { getStatus, isChatHidden } = useScriptTawkTo({ propertyId: 'YOUR_PROPERTY_ID', widgetId: 'YOUR_WIDGET_ID', }) getStatus() // 'online' | 'away' | 'offline' | undefined isChatHidden() // boolean, false before the widget has loaded ``` ## Identifying visitors `proxy.visitor = {...}` doesn't work for the same reason: unhead's script proxy has no `set` trap, so a property assignment through it never reaches the real `Tawk_API`. Use `setVisitor()`{lang="ts"} instead. It is pre-load only. Tawk honors `Tawk_API.visitor` before the embed script loads and ignores it afterwards, so once the embed has been requested the call warns and does nothing. Change identity after load with `window.Tawk_API.setAttributes({ name, email, phone, hash })`{lang="ts"}. ```ts const { proxy, setVisitor } = useScriptTawkTo({ propertyId: 'YOUR_PROPERTY_ID', widgetId: 'YOUR_WIDGET_ID', }) setVisitor({ name: 'Jane Doe', email: 'jane@example.com', // HMAC-SHA256 signature for Secure Mode, generated server-side hash: visitorHash, }) proxy.setAttributes({ plan: 'pro' }) proxy.addTags(['vip']) ``` ## Switching properties at runtime ```ts const { proxy } = useScriptTawkTo({ propertyId: 'YOUR_PROPERTY_ID', widgetId: 'YOUR_WIDGET_ID', }) proxy.switchWidget({ propertyId: 'OTHER_PROPERTY_ID', widgetId: 'OTHER_WIDGET_ID' }) ``` ::script-types :: ## Partytown Do not run [Tawk.to](http://Tawk.to) under Partytown. The widget renders DOM overlays (the chat bubble, prechat and full chat panels) directly, and the `window` `CustomEvent`s the reactive state and listeners depend on aren't configured for worker forwarding. --- - **Page:** X Embed - **Source:** https://scripts.nuxt.com/scripts/x-embed - **Description:** Server-rendered X posts without direct browser requests to X for post JSON or proxied images. [X (formerly Twitter)](https://x.com) is a social media platform for sharing posts. [``{lang="html"}](/scripts/x-embed){lang="html"} fetches post data through your Nuxt server and exposes it through slots. Post JSON and images pass through your origin rather than loading X's widget JavaScript. ::script-stats :: ::script-docs{embed} :: This registers the required server API routes (`/_scripts/embed/x` and `/_scripts/embed/x-image`) that handle fetching tweet data and proxying images. ## [``{lang="html"}](/scripts/x-embed){lang="html"} The post endpoint caches syndication responses for 10 minutes and rewrites profile photos, attached photos, entity media, quoted-post images, and video posters through the image endpoint. Video variant URLs remain unchanged, so rendering one in a `