---
title: "Nuxt Scripts 1.0: third-party scripts without the slowdown · vuejs.school"
description: "Nuxt Scripts 1.0 is stable. It loads analytics, maps and video embeds when you choose, waits for consent, and can serve them from your own domain."
canonical_url: "https://vuejs.school/community/blog/nuxt-scripts-1-0"
meta:
  "og:description": "Nuxt Scripts 1.0 is stable. It loads analytics, maps and video embeds when you choose, waits for consent, and can serve them from your own domain."
  "og:image": "https://vuejs.school/_og/d/a_~TnV4dCBTY3JpcHRzIDEuMDogdGhpcmQtcGFydHkgc2NyaXB0cyB3aXRob3V0IHRoZSBzbG93ZG93biDCtyB2dWVqcy5zY2hvb2w%2Cc_OgCard.takumi%2Ctitle_~TnV4dCBTY3JpcHRzIDEuMDogdGhpcmQtcGFydHkgc2NyaXB0cyB3aXRob3V0IHRoZSBzbG93ZG93bg%2Cdescription_~TnV4dCBTY3JpcHRzIDEuMCBpcyBzdGFibGUuIEl0IGxvYWRzIGFuYWx5dGljcywgbWFwcyBhbmQgdmlkZW8gZW1iZWRzIHdoZW4geW91IGNob29zZSwgd2FpdHMgZm9yIGNvbnNlbnQsIGFuZCBjYW4gc2VydmUgdGhlbSBmcm9tIHlvdXIgb3duIGRvbWFpbi4%2Ceyebrow_~UmVsZWFzZSBub3RlcyDCtyA2IG1pbiByZWFk%2Ctint_sky%2Cp_Ii9jb21tdW5pdHkvYmxvZy9udXh0LXNjcmlwdHMtMS0wIg%2Cs_Ngxb2JNGwmuXnyRZ.png"
  "og:image:alt": "Nuxt Scripts 1.0: third-party scripts without the slowdown · vuejs.school"
  "og:image:height": "600"
  "og:image:type": image/png
  "og:image:width": "1200"
  "og:title": "Nuxt Scripts 1.0: third-party scripts without the slowdown · vuejs.school"
  "twitter:image": "https://vuejs.school/_og/d/a_~TnV4dCBTY3JpcHRzIDEuMDogdGhpcmQtcGFydHkgc2NyaXB0cyB3aXRob3V0IHRoZSBzbG93ZG93biDCtyB2dWVqcy5zY2hvb2w%2Cc_OgCard.takumi%2Ctitle_~TnV4dCBTY3JpcHRzIDEuMDogdGhpcmQtcGFydHkgc2NyaXB0cyB3aXRob3V0IHRoZSBzbG93ZG93bg%2Cdescription_~TnV4dCBTY3JpcHRzIDEuMCBpcyBzdGFibGUuIEl0IGxvYWRzIGFuYWx5dGljcywgbWFwcyBhbmQgdmlkZW8gZW1iZWRzIHdoZW4geW91IGNob29zZSwgd2FpdHMgZm9yIGNvbnNlbnQsIGFuZCBjYW4gc2VydmUgdGhlbSBmcm9tIHlvdXIgb3duIGRvbWFpbi4%2Ceyebrow_~UmVsZWFzZSBub3RlcyDCtyA2IG1pbiByZWFk%2Ctint_sky%2Cp_Ii9jb21tdW5pdHkvYmxvZy9udXh0LXNjcmlwdHMtMS0wIg%2Cs_Ngxb2JNGwmuXnyRZ.png"
  "twitter:image:alt": "Nuxt Scripts 1.0: third-party scripts without the slowdown · vuejs.school"
---

> Index of every page on this site: [llms.txt](https://vuejs.school/llms.txt)

## How the module loads a script

Everything goes through `useScript()`, an extended version of the composable from Unhead, the library behind Nuxt's `useHead()`. Its defaults keep scripts out of the way:

- Scripts are not put into the server-rendered HTML, so they cannot hold up hydration.
- They load with the `onNuxtReady` trigger: once Nuxt has hydrated and the browser is idle.
- Script tags get `crossorigin="anonymous"` and `referrerpolicy="no-referrer"`, so the request for the script file sends no cookies and no page URL to the third party.

A trigger decides when a script loads, and any reactive value or promise can be one. Here a newsletter embed loads only when its section scrolls into view:

*app/components/NewsletterSignup.vue*

```
<script setup lang="ts">
const formEl = ref<HTMLElement>()

useScript('https://forms.example.com/embed.js', {
  trigger: useScriptTriggerElement({ trigger: 'visible', el: formEl }),
})
</script>

<template>
  <section ref="formEl">
    <h2>Get the newsletter</h2>
  </section>
</template>
```

The `formEl` template ref tells the trigger which element to watch. Other triggers cover the remaining cases:

- `trigger: 'manual'` waits until you call the returned `load()`, for example from an "Open chat" button.
- `useScriptTriggerIdleTimeout({ timeout: 5000 })` waits five seconds after Nuxt is ready.
- `useScriptTriggerInteraction({ events: ['scroll', 'click', 'keydown'] })` waits for the first of the events you list.

You can call a script's functions before it arrives through the `proxy` object that `useScript()` returns. A call such as `proxy.gtag('event', 'signup')` is queued and replayed once the script loads. If an ad blocker stops the script, the queued calls are dropped and nothing throws.

## What shipped in 1.0

Several of the changes concern the registry, the module's list of first-class integrations: analytics (Google Analytics, Plausible, Fathom, Umami, Matomo and others), Google Tag Manager and ad pixels, Google Maps, YouTube and Vimeo players, Stripe and PayPal, and chat widgets such as Intercom and Crisp. Each comes with a typed composable such as `useScriptGoogleAnalytics()`, options validated in development, and loading tuned for that script.

The release notes lead with these:

- **First-party mode.** Supported scripts are downloaded at build time and served from `/_scripts/assets/` on your domain, and their tracking requests go through a proxy on your server at `/_scripts/p/`, which removes cookies and shortens IP addresses before forwarding. It is on by default for every script that supports it.
- **Consent controls.** Consent-aware registry scripts take a typed `defaultConsent` option and return a `consent` object that speaks the vendor's own API, such as Google's Consent Mode for Analytics and Tag Manager or grant and revoke for the Meta Pixel.
- **Server-rendered social embeds.** `ScriptXEmbed`, `ScriptInstagramEmbed` and `ScriptBlueskyEmbed` fetch the post's data on the server and proxy its assets through your domain, in place of the vendor's embed script.
- **New registry scripts:** PostHog, Google reCAPTCHA v3, TikTok Pixel, Google Sign-In, Rybbit, Databuddy, Bing UET, Mixpanel, Vercel Analytics and Gravatar.
- **Smaller additions:** a `reload()` method for scripts that scan the page once, automatic Subresource Integrity hashes for bundled scripts, IDs read from `NUXT_PUBLIC_SCRIPTS_*` environment variables, a rebuilt DevTools panel, and large reworks of the Google Maps and YouTube player components.

## Google Analytics behind a consent banner

Install the module with `npx nuxt module add scripts`, then register the script in `nuxt.config.ts`:

*nuxt.config.ts*

```
export default defineNuxtConfig({
  modules: ['@nuxt/scripts'],
  scripts: {
    registry: {
      // sets up bundling, the proxy and types; a component decides when to load
      googleAnalytics: { id: 'G-XXXXXXXX', trigger: false },
    },
  },
})
```

`trigger: false` matters. It tells the module not to load the script globally, because a component will. The component holds the consent trigger, so the banner and the script share it:

*app/components/CookieBanner.vue*

```
<script setup lang="ts">
const consent = useScriptTriggerConsent()

useScriptGoogleAnalytics({
  scriptOptions: { trigger: consent },
})
</script>

<template>
  <div v-if="!consent.consented.value" role="region" aria-label="Cookie consent">
    <p>We use Google Analytics to see which pages people read. Nothing loads until you agree.</p>
    <button type="button" @click="consent.accept()">Accept analytics</button>
  </div>
</template>
```

Put `<CookieBanner />` in `app.vue`. Until the button is clicked, no Google request leaves the browser. `useScriptGoogleAnalytics()` reads the ID from the registry entry, so it is not repeated here.

In a real app, store the choice (a cookie or `localStorage`) and pass it as `useScriptTriggerConsent({ consent: savedChoice })`, so returning visitors are not asked again.

If you would rather load Google Analytics straight away in its restricted mode, use Consent Mode instead: pass `defaultConsent: { analytics_storage: 'denied', ad_storage: 'denied' }` and call `consent.update()` with `'granted'` values when the user agrees. Pick the load gate when your rule is "nothing before yes"; it is the simpler promise to keep.

## A YouTube embed that waits for a click

A YouTube iframe pulls in hundreds of kilobytes before anyone presses play. The facade components avoid that. They render a light placeholder, here the video's thumbnail, and swap in the real player only on an interaction:

*app/pages/talks.vue*

```
<template>
  <ScriptYouTubePlayer video-id="d_IFKP1Ofq0" ratio="16/9">
    <template #loading>
      <ScriptLoadingIndicator />
    </template>
    <template #error>
      <p>The video could not load. <a href="https://www.youtube.com/watch?v=d_IFKP1Ofq0">Watch it on YouTube</a>.</p>
    </template>
  </ScriptYouTubePlayer>
</template>
```

The player script loads on `mousedown` by default, so the click that starts loading is the click that starts the video. Embeds use `youtube-nocookie.com` unless you set the `cookies` prop. In 1.0 several players on one page no longer interfere with each other, and players are destroyed on unmount. If the video sits at the top of the page, add `above-the-fold` so the thumbnail loads eagerly.

## What first-party mode covers and where it stops

When a script is both bundled and proxied, as Google Analytics is, the browser makes no DNS lookup for the vendor, gets no third-party cookies, and ad blockers see same-origin requests, a side effect the release notes state openly.

Beyond cookies and IP addresses, what the proxy strips depends on the script. Ad pixels lose the most detail. Google Analytics, Microsoft Clarity and Hotjar keep the screen size and user agent that heatmaps need. The rest, such as Plausible or the YouTube player, lose nothing more.

Not every script gets all of this. Google Tag Manager, Segment, Crisp, Mixpanel and Bing UET are bundled but not proxied. Stripe, PayPal, reCAPTCHA and Google Sign-In load straight from the vendor, because their security checks need a direct connection. Nor does first-party mode replace consent: you still need it before loading a tracking script.

The proxy needs a server. With `nuxt generate` it switches itself off: scripts are still bundled and served from your domain, but their tracking requests go straight to the vendor. To turn the proxy off for one script, set `proxy: false` on its registry entry; `bundle: false` turns off both bundling and the proxy.

## Upgrading from 0.x

Where it can, 1.0 keeps old APIs working with deprecation warnings, and the [migration guide](https://scripts.nuxt.com/docs/migration-guide/v0-to-v1) lists each breaking change and deprecation. One change affects almost everyone: a registry entry no longer loads its script on its own. In 0.x, `googleAnalytics: { id: 'G-XXXXXX' }` meant "load on every page". In 1.0 it only sets up types, bundling and the proxy, and the build warns you. Add `trigger: 'onNuxtReady'` to keep the old behaviour, or `trigger: false` if a composable loads it.

The other changes are narrower:

- PayPal moved to its v6 SDK, and the v5 API is gone.
- The YouTube placeholder now uses `object-fit: cover`, and `width` and `height` no longer set the aspect ratio: use `ratio`.
- Google Maps lost its `markers` and placeholder props in favour of child components.
- The `true` shorthand for registry entries is deprecated.

Then install 1.0 and refresh the generated types, whose templates were reorganised:

```
npm install @nuxt/scripts@^1.0.0
npx nuxt prepare
```

Upgrade now if you are on 0.x: for most apps, the one required change is a config line the build points you to. First-party mode then turns itself on, so after you deploy to a server, look for requests to `/_scripts/p/` in the browser's Network tab to confirm the proxy works.

**Check it stuck**3 questions from the Modules, plugins and layers quiz.

[**Practice now**](https://vuejs.school/learn/nuxt/modules-and-the-nuxt-ecosystem?tab=quiz)

**Was this helpful?**

## **Keep reading**

[Release notes · 7 min read**What's new in Nuxt 4.6: nuxt/server, typed $fetch, cookie sessions and Vapor**](https://vuejs.school/community/blog/whats-new-in-nuxt-4-6) [Tutorial · 6 min read**useFetch or $fetch? Picking the right one in Nuxt**](https://vuejs.school/community/blog/usefetch-or-fetch-picking-the-right-one-in-nuxt)

```json
{"@context":"https://schema.org","@graph":[{"@id":"https://vuejs.school/#website","@type":"WebSite","description":"A free AI-instructed school for Vue.js. Short tutorials, runnable code and quizzes that adapt to you.","name":"vuejs.school","url":"https://vuejs.school/","publisher":{"@id":"https://vuejs.school/#identity"}},{"@id":"https://vuejs.school/community/blog/nuxt-scripts-1-0#webpage","@type":"WebPage","dateModified":"2026-10-27T19:31:40.253Z","datePublished":"2026-04-16","description":"Nuxt Scripts 1.0 is stable. It loads analytics, maps and video embeds when you choose, waits for consent, and can serve them from your own domain.","name":"Nuxt Scripts 1.0: third-party scripts without the slowdown","url":"https://vuejs.school/community/blog/nuxt-scripts-1-0","breadcrumb":{"@id":"https://vuejs.school/community/blog/nuxt-scripts-1-0#breadcrumb"},"isPartOf":{"@id":"https://vuejs.school/#website"},"potentialAction":[{"@type":"ReadAction","target":["https://vuejs.school/community/blog/nuxt-scripts-1-0"]}],"primaryImageOfPage":{"@id":"https://vuejs.school/#logo"}},{"@id":"https://vuejs.school/#identity","@type":"Organization","description":"A free AI-instructed school for Vue.js. Short tutorials, runnable code and quizzes that adapt to you.","name":"vuejs.school","url":"https://vuejs.school","logo":{"@id":"https://vuejs.school/#logo"}},{"@id":"https://vuejs.school/community/blog/nuxt-scripts-1-0#article","dateModified":"2026-10-27T19:31:40.253Z","datePublished":"2026-04-16","description":"Nuxt Scripts 1.0 is stable. It loads analytics, maps and video embeds when you choose, waits for consent, and can serve them from your own domain.","headline":"Nuxt Scripts 1.0: third-party scripts without the slowdown","inLanguage":"en","isAccessibleForFree":true,"thumbnailUrl":"https://vuejs.school/_og/d/a_~TnV4dCBTY3JpcHRzIDEuMDogdGhpcmQtcGFydHkgc2NyaXB0cyB3aXRob3V0IHRoZSBzbG93ZG93biDCtyB2dWVqcy5zY2hvb2w%2Cc_OgCard.takumi%2Ctitle_~TnV4dCBTY3JpcHRzIDEuMDogdGhpcmQtcGFydHkgc2NyaXB0cyB3aXRob3V0IHRoZSBzbG93ZG93bg%2Cdescription_~TnV4dCBTY3JpcHRzIDEuMCBpcyBzdGFibGUuIEl0IGxvYWRzIGFuYWx5dGljcywgbWFwcyBhbmQgdmlkZW8gZW1iZWRzIHdoZW4geW91IGNob29zZSwgd2FpdHMgZm9yIGNvbnNlbnQsIGFuZCBjYW4gc2VydmUgdGhlbSBmcm9tIHlvdXIgb3duIGRvbWFpbi4%2Ceyebrow_~UmVsZWFzZSBub3RlcyDCtyA2IG1pbiByZWFk%2Ctint_sky%2Cp_Ii9jb21tdW5pdHkvYmxvZy9udXh0LXNjcmlwdHMtMS0wIg%2Cs_Ngxb2JNGwmuXnyRZ.png","@type":["Article","BlogPosting"],"articleSection":["Release notes"],"author":{"@id":"https://vuejs.school/#identity"},"image":{"@id":"https://vuejs.school/#/schema/image/1"},"isPartOf":{"@id":"https://vuejs.school/community/blog/nuxt-scripts-1-0#webpage"},"mainEntityOfPage":{"@id":"https://vuejs.school/community/blog/nuxt-scripts-1-0#webpage"},"publisher":{"@id":"https://vuejs.school/#identity"}},{"@id":"https://vuejs.school/community/blog/nuxt-scripts-1-0#breadcrumb","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","name":"Blog","item":"https://vuejs.school/community/blog","position":1},{"@type":"ListItem","name":"Nuxt Scripts 1.0: third-party scripts without the slowdown","position":2}]},{"@id":"https://vuejs.school/#logo","@type":"ImageObject","caption":"vuejs.school","contentUrl":"https://vuejs.school/apple-touch-icon.png","inLanguage":"en","url":"https://vuejs.school/apple-touch-icon.png"},{"@id":"https://vuejs.school/#/schema/image/1","@type":"ImageObject","contentUrl":"https://vuejs.school/_og/d/a_~TnV4dCBTY3JpcHRzIDEuMDogdGhpcmQtcGFydHkgc2NyaXB0cyB3aXRob3V0IHRoZSBzbG93ZG93biDCtyB2dWVqcy5zY2hvb2w%2Cc_OgCard.takumi%2Ctitle_~TnV4dCBTY3JpcHRzIDEuMDogdGhpcmQtcGFydHkgc2NyaXB0cyB3aXRob3V0IHRoZSBzbG93ZG93bg%2Cdescription_~TnV4dCBTY3JpcHRzIDEuMCBpcyBzdGFibGUuIEl0IGxvYWRzIGFuYWx5dGljcywgbWFwcyBhbmQgdmlkZW8gZW1iZWRzIHdoZW4geW91IGNob29zZSwgd2FpdHMgZm9yIGNvbnNlbnQsIGFuZCBjYW4gc2VydmUgdGhlbSBmcm9tIHlvdXIgb3duIGRvbWFpbi4%2Ceyebrow_~UmVsZWFzZSBub3RlcyDCtyA2IG1pbiByZWFk%2Ctint_sky%2Cp_Ii9jb21tdW5pdHkvYmxvZy9udXh0LXNjcmlwdHMtMS0wIg%2Cs_Ngxb2JNGwmuXnyRZ.png","inLanguage":"en","url":"https://vuejs.school/_og/d/a_~TnV4dCBTY3JpcHRzIDEuMDogdGhpcmQtcGFydHkgc2NyaXB0cyB3aXRob3V0IHRoZSBzbG93ZG93biDCtyB2dWVqcy5zY2hvb2w%2Cc_OgCard.takumi%2Ctitle_~TnV4dCBTY3JpcHRzIDEuMDogdGhpcmQtcGFydHkgc2NyaXB0cyB3aXRob3V0IHRoZSBzbG93ZG93bg%2Cdescription_~TnV4dCBTY3JpcHRzIDEuMCBpcyBzdGFibGUuIEl0IGxvYWRzIGFuYWx5dGljcywgbWFwcyBhbmQgdmlkZW8gZW1iZWRzIHdoZW4geW91IGNob29zZSwgd2FpdHMgZm9yIGNvbnNlbnQsIGFuZCBjYW4gc2VydmUgdGhlbSBmcm9tIHlvdXIgb3duIGRvbWFpbi4%2Ceyebrow_~UmVsZWFzZSBub3RlcyDCtyA2IG1pbiByZWFk%2Ctint_sky%2Cp_Ii9jb21tdW5pdHkvYmxvZy9udXh0LXNjcmlwdHMtMS0wIg%2Cs_Ngxb2JNGwmuXnyRZ.png"}]}
```