Skip to main content
6 min read

First-Party Tracking with Vercel

Set up a first-party proxy on Vercel. This guide covers both vercel.json rewrites and Edge Middleware options.

Difficulty: Easy - Works with any framework deployed on Vercel. Just add a config file.

Why This Works (Same-Origin)

This approach uses /api/_z/script.js which is on the same origin as your website. Firefox ETP and other tracking protections only block cross-origin requests.

  • Browser sees: yourdomain.com/api/_z/script.js → Same origin ✅
  • Vercel rewrites the request to Zenovay server-side (browser never sees this)
  • All tracking protection is bypassed because the request is first-party

Before You Start

Make sure you have:

  • A project deployed on Vercel
  • Your Zenovay tracking code (found in Dashboard → Your Site → Settings)
  • Access to your project's configuration files

Your Tracking Code Format

Your tracking code looks like: ZV_XXXXXXXXXX

  • Starts with ZV_
  • Followed by 10 characters (letters and numbers)
  • CASE-SENSITIVE - copy it exactly

Example: ZV_Q8U0GYD70WR


Option 1: Using vercel.json (Simplest)

The easiest approach is to add rewrites to your vercel.json:

Step 1: Create or Update vercel.json

Add the following rewrite rule to your vercel.json file in your project root:

vercel.jsonJSON
{
"rewrites": [
  {
    "source": "/api/_z/:path*",
    "destination": "https://api.zenovay.com/fp/:path*"
  }
]
}

If you already have a vercel.json, add the rewrite to your existing rewrites array:

vercel.json (with existing config)JSON
{
"buildCommand": "npm run build",
"outputDirectory": "dist",
"rewrites": [
  {
    "source": "/api/_z/:path*",
    "destination": "https://api.zenovay.com/fp/:path*"
  }
]
}

Step 2: Add the Tracking Script

Your HTMLHTML
<script defer
data-tracking-code="YOUR_TRACKING_CODE"
src="/api/_z/script.js">
</script>

Step 3: Deploy

TerminalBash
vercel deploy

That's it! The rewrite handles everything automatically.


Option 2: Using Edge Middleware (More Control)

Edge Middleware gives you more control over the proxy behavior, including the ability to forward the real client IP.

Step 1: Create Middleware File

Create a middleware.ts file in your project root (or src/ if you use that structure):

middleware.tsTypeScript
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'

export function middleware(request: NextRequest) {
// Only handle /api/_z/* routes
if (!request.nextUrl.pathname.startsWith('/api/_z/')) {
  return NextResponse.next()
}

// Build the target URL
const path = request.nextUrl.pathname.replace('/api/_z/', '')
const targetUrl = new URL(`https://api.zenovay.com/fp/${path}`)
targetUrl.search = request.nextUrl.search

// Create the rewrite response
const response = NextResponse.rewrite(targetUrl)

// Forward the real client IP for accurate geolocation
const clientIP = request.headers.get('x-forwarded-for')?.split(',')[0]
              || request.headers.get('x-real-ip')
              || ''

if (clientIP) {
  response.headers.set('X-Zenovay-Real-IP', clientIP)
}

return response
}

export const config = {
// Only run middleware on /api/_z/* paths
matcher: '/api/_z/:path*',
}

Why Edge Middleware? Vercel's vercel.json rewrites work well, but middleware allows you to:

  • Forward the real client IP for accurate geolocation
  • Add custom headers
  • Implement conditional logic
  • Log requests

Step 2: Add the Tracking Script

Your HTMLHTML
<script defer
data-tracking-code="YOUR_TRACKING_CODE"
src="/api/_z/script.js">
</script>

Step 3: Deploy

TerminalBash
vercel deploy

Framework-Specific Examples

React (Create React App)

vercel.jsonJSON
{
"rewrites": [
  { "source": "/api/_z/:path*", "destination": "https://api.zenovay.com/fp/:path*" }
]
}
public/index.htmlJSX
<!DOCTYPE html>
<html lang="en">
<head>
  <script defer data-tracking-code="YOUR_TRACKING_CODE" src="/api/_z/script.js"></script>
  <!-- other head elements -->
</head>
<body>
  <div id="root"></div>
</body>
</html>

Vue.js

vercel.jsonJSON
{
"rewrites": [
  { "source": "/api/_z/:path*", "destination": "https://api.zenovay.com/fp/:path*" }
]
}
public/index.htmlHTML
<!DOCTYPE html>
<html lang="en">
<head>
  <script defer data-tracking-code="YOUR_TRACKING_CODE" src="/api/_z/script.js"></script>
</head>
<body>
  <div id="app"></div>
</body>
</html>

SvelteKit

vercel.jsonJSON
{
"rewrites": [
  { "source": "/api/_z/:path*", "destination": "https://api.zenovay.com/fp/:path*" }
]
}
src/app.htmlSVELTE
<!DOCTYPE html>
<html lang="en">
<head>
  <script defer data-tracking-code="YOUR_TRACKING_CODE" src="/api/_z/script.js"></script>
  %sveltekit.head%
</head>
<body>
  <div>%sveltekit.body%</div>
</body>
</html>

Nuxt 3

vercel.jsonJSON
{
"rewrites": [
  { "source": "/api/_z/:path*", "destination": "https://api.zenovay.com/fp/:path*" }
]
}
app.vueVUE
<script setup>
useHead({
script: [
  {
    src: '/api/_z/script.js',
    defer: true,
    'data-tracking-code': 'YOUR_TRACKING_CODE'
  }
]
})
</script>

<template>
<NuxtPage />
</template>

Verify It's Working

Check the Network Tab

  1. Visit your deployed Vercel site
  2. Open DevTools (F12)
  3. Go to the Network tab
  4. Reload the page
  5. Look for /api/_z/script.js

You should see:

  • Status: 200
  • Domain: Your Vercel domain
  • Response: JavaScript code

Check Vercel Function Logs

If using middleware:

  1. Go to Vercel Dashboard → Your Project
  2. Click Logs (or Functions tab)
  3. Look for requests to /api/_z/*

Test in Firefox Strict Mode

  1. Open Firefox
  2. Settings → Privacy & Security → Enhanced Tracking Protection: Strict
  3. Visit your site
  4. Verify the script loads successfully

Troubleshooting

Rewrite Returns 404

Cause: The rewrite path doesn't match or vercel.json isn't being read.

Solution:

  1. Verify vercel.json is in your project root
  2. Check the path pattern matches exactly: /api/_z/:path*
  3. Redeploy your project

Middleware Not Running

Cause: The matcher pattern doesn't match, or middleware file is in the wrong location.

Solution:

  1. Ensure middleware.ts is in the project root (not inside pages/ or app/)
  2. Check the matcher pattern: '/api/_z/:path*'
  3. For src/ directory projects, the file should be at src/middleware.ts

Geolocation Shows Wrong Location

Cause: Client IP not being forwarded.

Solution: Use the Edge Middleware approach (Option 2) which forwards the x-forwarded-for header:

const clientIP = request.headers.get('x-forwarded-for')?.split(',')[0] || ''
response.headers.set('X-Zenovay-Real-IP', clientIP)

Script Loads But No Data

Cause: Tracking code mismatch or JavaScript errors.

Solution:

  1. Verify data-tracking-code matches your code in Zenovay dashboard (case-sensitive)
  2. Check browser console for JavaScript errors
  3. Ensure the website domain is added to Zenovay

Complete Example Project

Here's a minimal complete setup:

Project StructureTEXT
my-vercel-project/
├── vercel.json
├── public/
│   └── index.html
├── src/
│   └── App.jsx
└── package.json
vercel.jsonJSON
{
"rewrites": [
  {
    "source": "/api/_z/:path*",
    "destination": "https://api.zenovay.com/fp/:path*"
  }
]
}
public/index.htmlHTML
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>My App</title>
  <script defer data-tracking-code="YOUR_TRACKING_CODE" src="/api/_z/script.js"></script>
</head>
<body>
  <div id="root"></div>
  <script type="module" src="/src/main.jsx"></script>
</body>
</html>

Final Checklist

Before you're done, verify ALL of these:

  • vercel.json has the rewrite rule (or middleware is set up)
  • Script tag uses /api/_z/script.js (not the direct Zenovay URL)
  • data-tracking-code attribute contains your correct tracking code
  • Project is deployed (not just running locally)
  • Tested in Firefox with Enhanced Tracking Protection set to Strict
  • Visits appearing in Zenovay dashboard

Next Steps

Was this page helpful?