---
title: "First-Party Mode: Privacy Focused Proxy · Nuxt Scripts"
meta:
  description: "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."
  "og:description": "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."
  "og:title": "First-Party Mode: Privacy Focused Proxy · Nuxt Scripts"
---

```

Nuxt Scripts on GitHub

**Guides**# **First-Party Mode: Privacy Focused Proxy**[Copy for LLMs](https://scripts.nuxt.com/docs/guides/first-party.md) ## The Problem Every third-party script your site loads connects your users directly to external servers. Each request shares the user's IP address, and many scripts go further: the X Pixel accesses 9 browser fingerprinting APIs (including `navigator.getBattery()`), sets 5 tracking cookies (`muc_ads`, `guest_id_marketing`, `guest_id_ads`, `personalization_id`, `guest_id`), and phones home to 3 separate domains. Microsoft Clarity reads 10 fingerprinting APIs across 3 domains. Even Google Analytics at 154 KB sends data that can be correlated across sites. Ad blockers rightfully block these requests, which breaks analytics for sites that depend on them. ## How It Works First-party mode combines three layers that work together automatically. Each script declares which capabilities it supports; the module enables them with zero config. ### 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. This eliminates extra DNS lookups, avoids CORS overhead, and means the initial script load never touches a third-party server. Bundled scripts use content-addressed hashes for long-term browser caching. ### Reverse Proxy Runtime requests (analytics beacons, pixel fires, tracking calls) are intercepted and forwarded through Nitro server routes at `/_scripts/p/`. The module rewrites URLs at two levels: 1. **AST rewriting** at build time: third-party domains in the bundled script source are replaced with your proxy path 2. **Runtime intercept** on the client: a plugin patches `fetch`, `sendBeacon`, `XMLHttpRequest`, and `Image` to catch any dynamic URLs the AST rewriter missed Users never connect to third-party servers directly, so no third-party cookies are set and ad blockers don't interfere. ### Anonymisation Before forwarding proxied requests, the handler strips identifying data. IP addresses are anonymised to subnet level. Sensitive headers (cookies, auth tokens) are always removed. Additional anonymisation (user agent, screen dimensions, hardware fingerprints) varies per script based on what would break functionality. Even with minimal anonymisation, the reverse proxy alone prevents direct browser connections, eliminates third-party cookies, and ensures requests appear same-origin. ## Usage First-party mode is **auto-enabled** for all scripts that support it. Adding a script to the registry enables infrastructure (proxy routes, bundling, types, composables) without auto-loading it: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: { domain: 'mysite.com', 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 Every proxied script defaults to a privacy tier based on what level of anonymisation is safe for that script's functionality. Three tiers cover all scripts: | **Tier** | **What's anonymised** | **Scripts** |
| --- | --- | --- | | **Full** | IP, user agent, language, screen, timezone, hardware fingerprints | Meta Pixel, TikTok Pixel, X Pixel, Snapchat Pixel, Reddit Pixel | | **Heatmap-safe** | IP, language, hardware fingerprints (preserves screen and user agent for session replay) | Google Analytics, Microsoft Clarity, Hotjar | | **IP only** | IP addresses anonymised to subnet level | Plausible, PostHog, Umami, Fathom, CF Web Analytics, Vercel Analytics, Rybbit, Databuddy, Matomo, Intercom, YouTube, Vimeo, Gravatar, Carbon Ads, Lemon Squeezy, Google AdSense | Sensitive headers (`cookie`, `authorization`) are **always** stripped regardless of tier. ### Six Privacy Flags Each tier maps to a combination of six flags: | **Flag** | **What it does** |
| --- | --- | | `ip` | Anonymises 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)`) | | `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` | Anonymises canvas/webgl/audio fingerprints, plugin/font lists, browser versions, and device info | ### Tier Flag Matrix | **Flag** | **IP Only** | **Heatmap-safe** | **Full** |
| --- | --- | --- | --- | | `ip` |  |  |  | | `userAgent` |  |  |  | | `language` |  |  |  | | `screen` |  |  |  | | `timezone` |  |  |  | | `hardware` |  |  |  |**IP Only** anonymises the IP to a /24 subnet (city-level geo accuracy). **Heatmap-safe** strips language and hardware fingerprints while preserving user agent and screen dimensions needed for session replay tools. **Full** strips everything, used for ad pixels where no analytics reporting depends on raw client data. ### Global Override Override all per-script privacy defaults at once using the top-level `privacy` option:nuxt.config.ts```
export default defineNuxtConfig({
  scripts: {
    privacy: true, // Full anonymisation for ALL scripts
  }
})
``` Or selectively override specific flags:nuxt.config.ts```
export default defineNuxtConfig({
  scripts: {
    privacy: { ip: true }, // Anonymise IP for all scripts, rest uses per-script defaults
  }
})
```### Per-Script Privacy Override Override the privacy tier for a specific script by adding `privacy` to its registry entry:nuxt.config.ts```
export default defineNuxtConfig({
  scripts: {
    registry: {
      // Full anonymisation for Meta Pixel (overrides its default)
      metaPixel: { id: '123456', privacy: { ip: true, userAgent: true, screen: true } },
      // IP-only for self-hosted PostHog where you control the data
      posthog: { apiKey: 'phc_xxx', privacy: { ip: true } },
    }
  }
})
```### Disabling Anonymisation Disable anonymisation per-script or globally. Routing still applies (requests still go through your server), only the data stripping is turned off:nuxt.config.ts```
export default defineNuxtConfig({
  scripts: {
    privacy: false, // No anonymisation for any script (routing still active)
  }
})
```## Opting Out ### Per-Script Disable proxying for a specific script using `proxy: false` in its registry config:nuxt.config.ts```
export default defineNuxtConfig({
  scripts: {
    registry: {
      plausibleAnalytics: { domain: 'mysite.com', proxy: false },
      googleAnalytics: { id: 'G-XXXXXX' }, // still proxied
    }
  }
})
``` Setting `bundle: false` on a bundled script also disables its proxy. The reverse proxy depends on AST URL rewriting, which requires the bundled script source. Without bundling, there are no URLs to rewrite.nuxt.config.ts```
export default defineNuxtConfig({
  scripts: {
    registry: {
      // Disables both bundling AND proxying for this script
      googleAnalytics: { id: 'G-XXXXXX', bundle: false },
    }
  }
})
``` This does not apply to proxy-only scripts (PostHog, Matomo, etc.) that use config injection instead of URL rewriting. Those scripts can be proxied without bundling. ### Static Hosting (SSG) The reverse proxy requires a **server runtime**. For static deployments (`nuxt generate`), the proxy is automatically disabled. Scripts are still bundled and served from your domain, but runtime collection requests (analytics beacons, pixel fires) go directly to third-party servers. If you need proxying with static hosting, configure platform-level rewrites manually. The pattern is `/_scripts/p/<domain>/:path*` → `https://<domain>/:path*`:vercel.json```
{
  "rewrites": [
    { "source": "/_scripts/p/www.google-analytics.com/:path*", "destination": "https://www.google-analytics.com/:path*" },
    { "source": "/_scripts/p/www.googletagmanager.com/:path*", "destination": "https://www.googletagmanager.com/:path*" },
    { "source": "/_scripts/p/connect.facebook.net/:path*", "destination": "https://connect.facebook.net/:path*" }
  ]
}
``` The same pattern applies to [**~~Netlify~~**](https://netlify.com) (`[[redirects]]` with `:splat`) and Cloudflare Pages (`_redirects` file). Only include rewrites for scripts you use. Check Nuxt DevTools → Scripts or your Nitro server logs for the exact domains registered. Platform-level rewrites bypass the privacy anonymisation layer. The proxy handler only runs in a Nitro server runtime. ## Supported Scripts ### 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** | [**~~Google Analytics~~**](https://scripts.nuxt.com/scripts/google-analytics), [**~~Plausible~~**](https://scripts.nuxt.com/scripts/plausible-analytics), [**~~Cloudflare Web Analytics~~**](https://scripts.nuxt.com/scripts/cloudflare-web-analytics), [**~~Umami~~**](https://scripts.nuxt.com/scripts/umami-analytics), [**~~Fathom~~**](https://scripts.nuxt.com/scripts/fathom-analytics), [**~~Rybbit~~**](https://scripts.nuxt.com/scripts/rybbit-analytics), [**~~Databuddy~~**](https://scripts.nuxt.com/scripts/databuddy-analytics), [**~~Vercel Analytics~~**](https://scripts.nuxt.com/scripts/vercel-analytics), [**~~Microsoft Clarity~~**](https://scripts.nuxt.com/scripts/clarity), [**~~Hotjar~~**](https://scripts.nuxt.com/scripts/hotjar) | | **Ad Pixels** | [**~~Meta Pixel~~**](https://scripts.nuxt.com/scripts/meta-pixel), [**~~TikTok Pixel~~**](https://scripts.nuxt.com/scripts/tiktok-pixel), [**~~X Pixel~~**](https://scripts.nuxt.com/scripts/x-pixel), [**~~Snapchat Pixel~~**](https://scripts.nuxt.com/scripts/snapchat-pixel), [**~~Reddit Pixel~~**](https://scripts.nuxt.com/scripts/reddit-pixel), [**~~Google AdSense~~**](https://scripts.nuxt.com/scripts/google-adsense) | | **Video** | [**~~YouTube Player~~**](https://scripts.nuxt.com/scripts/youtube-player), [**~~Vimeo Player~~**](https://scripts.nuxt.com/scripts/vimeo-player) | | **Utility** | [**~~Intercom~~**](https://scripts.nuxt.com/scripts/intercom), [**~~Gravatar~~**](https://scripts.nuxt.com/scripts/gravatar) | ### Proxy Only (Not Bundled) The module can't bundle these scripts at build time, but still proxies their collection requests through your server: | **Script** | **How it works** |
| --- | --- | | [**~~PostHog~~**](https://scripts.nuxt.com/scripts/posthog) | SDK installed via [**~~npm~~**](https://npmjs.com). Proxy endpoint auto-injected via `apiHost` config. | | [**~~Matomo~~**](https://scripts.nuxt.com/scripts/matomo-analytics) | Self-hosted; bundling breaks the script. Proxy routes registered for collection requests. | | [**~~Carbon Ads~~**](https://scripts.nuxt.com/scripts/carbon-ads) | Ad serving script. Proxy routes registered for collection requests. | | [**~~Lemon Squeezy~~**](https://scripts.nuxt.com/scripts/lemon-squeezy) | Payment widget. Proxy routes registered for collection requests. | ### Bundle Only (No Proxy) These scripts are served from your domain but their runtime requests still go directly to third-party servers: | **Script** | **Why proxy isn't supported** |
| --- | --- | | [**~~Google Tag Manager~~**](https://scripts.nuxt.com/scripts/google-tag-manager) | GTM's core function is loading other scripts at runtime. Those runtime scripts bypass build-time rewriting. | | [**~~Segment~~**](https://scripts.nuxt.com/scripts/segment) | SDK constructs API URLs dynamically, bypassing request interception. | | [**~~Crisp~~**](https://scripts.nuxt.com/scripts/crisp) | SDK loads secondary scripts and CSS at runtime from `client.crisp.chat`. | | [**~~Mixpanel~~**](https://scripts.nuxt.com/scripts/mixpanel-analytics) | No proxy integration yet. | | [**~~Bing UET~~**](https://scripts.nuxt.com/scripts/bing-uet) | No proxy integration yet. | Bundle-only scripts still benefit from being served as first-party assets (faster loading, no CORS, reduced external connections at page load). ### Excluded (Direct Loading Only) These scripts require direct browser connections for security: | **Script** | **Reason** |
| --- | --- | | **Stripe** | Fraud detection requires real client fingerprints | | **PayPal** | Fraud detection requires real client fingerprints | | **Google reCAPTCHA** | Bot detection requires real browser fingerprints | | **Google Sign-In** | Auth integrity requires direct connection | These scripts still work normally, they connect directly to the third-party server instead of routing through your domain. ## Partytown (Web Worker) Load individual scripts off the main thread by setting `partytown: true` per-script:nuxt.config.ts```
export default defineNuxtConfig({
  modules: ['@nuxtjs/partytown', '@nuxt/scripts'],
  scripts: {
    registry: {
      plausibleAnalytics: { domain: 'example.com', partytown: true },
      fathomAnalytics: { site: 'XXXXX', partytown: true },
    }
  }
})
``` Forward arrays are auto-configured for supported scripts. You must install `@nuxtjs/partytown`.GA4 has [**~~known issues~~**](https://github.com/BuilderIO/partytown/issues/583) with Partytown. GTM is not compatible (requires DOM access). Consider Plausible, Fathom, or Umami instead. ## Consent Integration First-party mode controls _where_ requests go. [**~~Consent triggers~~**](https://scripts.nuxt.com/docs/guides/consent) control _when_ scripts load. Use both:```
<script setup lang="ts">const trigger=useScriptTriggerConsent();useScriptGoogleAnalytics({id:`G-XXXXXX`,scriptOptions:{trigger}});</script>
```### Third-Party Consent Managers For tools like OneTrust, CookieBot, or Osano, bind their consent signal to a reactive ref:```
<script setup lang="ts">const hasAnalyticsConsent=ref(!1);onMounted(()=>{window.OneTrust?.OnConsentChanged(()=>{hasAnalyticsConsent.value=window.OnetrustActiveGroups?.includes(`C0002`)??!1})}),useScriptGoogleAnalytics({id:`G-XXXXXX`,scriptOptions:{trigger:useScriptTriggerConsent({consent:hasAnalyticsConsent})}});</script>
``` Or use `trigger: 'manual'` in your registry config and call `$script.load()` when consent is granted:nuxt.config.ts```
export default defineNuxtConfig({
  scripts: {
    registry: {
      // Infrastructure only, load manually after consent
      googleAnalytics: { id: 'G-XXXXXX' },
    }
  }
})
```app.vue```
<script setup lang="ts">const{$script}=useScriptGoogleAnalytics();function onConsentGranted(){$script.load()}</script>
``` See the [**~~Consent Management Guide~~**](https://scripts.nuxt.com/docs/guides/consent) for full details on `useScriptTriggerConsent()`. First-party mode does **not** bypass GDPR consent requirements. You still need user consent before loading tracking scripts. ## 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 | The reverse proxy is automatically disabled for SSG. Use platform rewrites or switch to server mode. See [**~~Static Hosting~~**](#static-hosting-ssg) | | Stale script | `rm -rf .nuxt/cache/scripts` and rebuild | | Build download fails | Set `assets.fallbackOnSrcOnBundleFail:true` to fall back to direct loading | | Debugging | Open Nuxt DevTools → Scripts to see proxy routes and privacy status | | Geo accuracy reduced | IP anonymisation uses /24 subnets, which gives city-level accuracy. Set `privacy: false` per-script or globally to forward exact IPs | | `bundle: false` disabled my proxy | For bundled scripts, the proxy depends on AST URL rewriting which requires the bundled source. Use `proxy: false` instead if you only want to disable the proxy | | Per-script opt-out not working | For scripts with auto-inject (Plausible, PostHog, Umami, Rybbit, Databuddy), use `proxy: false` in the registry config | [~~Edit this page~~](https://github.com/nuxt/scripts/edit/main/docs/content/docs/1.guides/2.first-party.md) [~~Markdown For LLMs~~](https://scripts.nuxt.com/docs/guides/first-party.md) [**Warmup Strategy** Customize the preload or preconnect strategy used for your scripts.](https://scripts.nuxt.com/docs/guides/warmup) [**Consent Management** Learn how to get user consent before loading scripts.](https://scripts.nuxt.com/docs/guides/consent)**On this page **- [The Problem](#the-problem) - [How It Works](#how-it-works) - [Usage](#usage) - [Privacy Tiers](#privacy-tiers) - [Opting Out](#opting-out) - [Supported Scripts](#supported-scripts) - [Partytown (Web Worker)](#partytown-web-worker) - [Consent Integration](#consent-integration) - [Troubleshooting](#troubleshooting)