First-Party Mode
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 First-Party Mode Works
First-party mode puts you in control of your users' requests:
- Build time: Scripts are downloaded and served from your domain. Collection URLs are rewritten to local paths (e.g.,
google-analytics.com/g/collect→/_scripts/p/ga/g/collect) - Runtime: Nitro proxies requests back to the original endpoints, anonymizing user data before forwarding
Your users never connect directly to third-party servers. Third parties see your server's IP, not your users'. Requests are same-origin so no third-party cookies are set, and ad blockers don't interfere.
Usage
First-party mode is enabled by default. Add scripts to your registry:
export default defineNuxtConfig({
scripts: {
registry: {
googleAnalytics: { id: 'G-XXXXXX' },
metaPixel: { id: '123456' },
}
}
})
To disable globally:
export default defineNuxtConfig({
scripts: {
firstParty: false
}
})
Or per script:
useScriptGoogleAnalytics({
id: 'G-XXXXXX',
scriptOptions: { firstParty: false }
})
Privacy Controls
Each script in the registry declares its own privacy defaults based on what data it needs. Privacy is controlled by six flags:
| 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)) |
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 |
Sensitive headers (cookie, authorization) are always stripped regardless of privacy settings.
Per-Script Defaults
Four privacy presets cover all scripts:
| Preset | Flags | Scripts |
|---|---|---|
| Full | all flags | Meta Pixel, TikTok Pixel, X Pixel, Snapchat Pixel, Reddit Pixel |
| Heatmap-safe | ip, language, hardware | Google Analytics, Microsoft Clarity, Hotjar |
| IP only | ip | Intercom, Crisp, Gravatar, Stripe, PayPal, YouTube, Vimeo, reCAPTCHA, Google Sign-In |
| None | - | GTM, PostHog, Plausible, Umami, Rybbit, Databuddy, Fathom, CF Web Analytics, Vercel, Segment, Carbon Ads, Lemon Squeezy, Matomo |
Global Override
Override all per-script defaults at once:
export default defineNuxtConfig({
scripts: {
firstParty: {
privacy: true, // Full anonymize for ALL scripts
}
}
})
Or selectively override specific flags:
export default defineNuxtConfig({
scripts: {
firstParty: {
privacy: { ip: true }, // Anonymize IP for all scripts, rest uses per-script defaults
}
}
})
Supported Scripts
First-party mode supports all registry scripts:
| Category | Scripts |
|---|---|
| Analytics | Google Analytics, Google Tag Manager, PostHog, Plausible, Cloudflare Web Analytics, Umami, Fathom, Rybbit, Databuddy, Vercel Analytics, Matomo, Segment, Microsoft Clarity, Hotjar |
| Ad Pixels | Meta Pixel, TikTok Pixel, X Pixel, Snapchat Pixel, Reddit Pixel, Google AdSense, Carbon Ads |
| Payments | Stripe, PayPal, Lemon Squeezy |
| Video | YouTube Player, Vimeo Player |
| Utility | Google reCAPTCHA, Google Sign-In, Intercom, Crisp, Gravatar |
Static Hosting
First-party mode requires a server runtime for the proxy endpoints. For static deployments (nuxt generate), scripts are still bundled with rewritten URLs but you'll need to configure platform rewrites manually.
The pattern is /_scripts/p/<alias>/:path* → https://<original-domain>/:path*. Check Nuxt DevTools → Scripts or your Nitro server logs for the exact routes registered for your scripts.
Example for Vercel:
{
"rewrites": [
{ "source": "/_scripts/p/ga/:path*", "destination": "https://www.google-analytics.com/:path*" },
{ "source": "/_scripts/p/gtm/:path*", "destination": "https://www.googletagmanager.com/:path*" },
{ "source": "/_scripts/p/meta/:path*", "destination": "https://connect.facebook.net/:path*" }
]
}
The same pattern applies to Netlify ([[redirects]] with :splat) and Cloudflare Pages (_redirects file). Only include rewrites for scripts you use.
Consent Integration
First-party mode controls where requests go. Consent triggers control when scripts load. Use both:
<script setup>
const trigger = useScriptTriggerConsent()
useScriptGoogleAnalytics({
id: 'G-XXXXXX',
scriptOptions: { trigger }
})
</script>
Troubleshooting
| Problem | Fix |
|---|---|
| Analytics not tracking | Check DevTools → Network for /_scripts/p/ requests. Check Nitro server logs for proxy errors |
| 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 first-party status |