Skip to main content
7 min read

First-Party Tracking with Netlify

Set up a first-party proxy on Netlify using simple redirect rules. Works with any static site or framework.

Difficulty: Easy - Just add a few lines to 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 ✅
  • Netlify redirects the request to Zenovay server-side (browser never sees this)
  • All tracking protection is bypassed because the request is first-party

The proxied script stops honouring Do Not Track

Every bundle served through /api/_z/script.js ships with ignoreDNT enabled, so a visitor whose browser sends the legacy Do Not Track signal is still tracked on a proxied site, whether or not you asked for that. Global Privacy Control is never overridden and is still honoured. If Do Not Track matters for your audience, serve the standard script from api.zenovay.com instead. See Privacy-first.


Before You Start

Make sure you have:

  • A project deployed on Netlify
  • Your Zenovay tracking code (found under Domains → your site → General, in the Tracking script card)
  • Access to your project's build configuration

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

Domain General settings page showing the Tracking script card with the script snippet and First-Party tab
Find your tracking code and the First-Party tab under Domains → your site → General.

Option 1: Using _redirects File (Simplest)

The easiest way to set up the proxy is using Netlify's _redirects file.

Step 1: Create the Redirects File

Create a _redirects file in your public/ folder (or wherever your static assets go):

public/_redirectsTEXT
# Zenovay first-party tracking proxy
/api/_z/*  https://api.zenovay.com/fp/:splat  200

How it works: The 200 status code makes this a rewrite (proxy) instead of a redirect. The browser sees your domain, but Netlify fetches from Zenovay's API behind the scenes.

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
netlify deploy --prod

Or push to your Git repository if you have continuous deployment configured.


Option 2: Using netlify.toml (More Explicit)

For more complex configurations, use netlify.toml:

Step 1: Create or Update netlify.toml

Add the redirect rule to your netlify.toml in your project root:

netlify.tomlTOML
# Zenovay first-party tracking proxy
[[redirects]]
from = "/api/_z/*"
to = "https://api.zenovay.com/fp/:splat"
status = 200
force = true

If you already have a netlify.toml, add the redirect to your existing file:

netlify.toml (with existing config)TOML
[build]
command = "npm run build"
publish = "dist"

# Zenovay first-party tracking proxy
[[redirects]]
from = "/api/_z/*"
to = "https://api.zenovay.com/fp/:splat"
status = 200
force = true

# Your other redirects...
[[redirects]]
from = "/*"
to = "/index.html"
status = 200

Order matters! Put the Zenovay redirect BEFORE any catch-all redirects (like SPA fallbacks). Netlify processes redirects in order.

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
netlify deploy --prod

Framework-Specific Examples

React (Create React App)

public/_redirectsTEXT
/api/_z/*  https://api.zenovay.com/fp/:splat  200
public/index.htmlHTML
<!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 (Vite)

public/_redirectsTEXT
/api/_z/*  https://api.zenovay.com/fp/:splat  200
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>
  <script type="module" src="/src/main.js"></script>
</body>
</html>

Gatsby

netlify.tomlTOML
[build]
command = "gatsby build"
publish = "public"

[[redirects]]
from = "/api/_z/*"
to = "https://api.zenovay.com/fp/:splat"
status = 200
force = true
gatsby-ssr.jsJSX
import React from 'react'

export const onRenderBody = ({ setHeadComponents }) => {
setHeadComponents([
  <script
    key="zenovay"
    defer
    data-tracking-code="YOUR_TRACKING_CODE"
    src="/api/_z/script.js"
  />
])
}

Hugo

static/_redirectsTEXT
/api/_z/*  https://api.zenovay.com/fp/:splat  200
layouts/partials/head.htmlHTML
<script defer data-tracking-code="YOUR_TRACKING_CODE" src="/api/_z/script.js"></script>

11ty (Eleventy)

src/_redirectsTEXT
/api/_z/*  https://api.zenovay.com/fp/:splat  200

Make sure _redirects is copied to output by adding to your .eleventy.js:

.eleventy.jsJavaScript
module.exports = function(eleventyConfig) {
eleventyConfig.addPassthroughCopy("src/_redirects");
// ... rest of config
}

Verify It's Working

Check in Netlify Dashboard

  1. Go to your site in Netlify Dashboard
  2. Click Site configurationRedirects
  3. You should see your /api/_z/* redirect listed

Check the Network Tab

  1. Visit your deployed 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 Netlify domain
  • Response: JavaScript code

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

Redirect Returns 404

Cause: The _redirects file isn't being published or is in the wrong location.

Solution:

  1. Make sure _redirects is in your publish directory (usually public/ or dist/)
  2. For build tools, ensure the file is copied during build
  3. Check Netlify Dashboard → Redirects to see if it's recognized

Redirect Causes a Loop

Cause: Another redirect is interfering.

Solution:

  1. Use force = true in netlify.toml to override other rules
  2. Make sure the Zenovay redirect comes BEFORE catch-all rules
  3. Check for conflicting _redirects and netlify.toml rules

Script Loads But Returns HTML

Cause: A catch-all SPA redirect is intercepting the request.

Solution: Put the Zenovay redirect BEFORE your SPA fallback:

netlify.tomlTOML
# This MUST come first
[[redirects]]
from = "/api/_z/*"
to = "https://api.zenovay.com/fp/:splat"
status = 200
force = true

# SPA fallback comes after
[[redirects]]
from = "/*"
to = "/index.html"
status = 200

Geolocation Shows Wrong Location

Cause: Netlify's proxy doesn't automatically forward client IP.

Solution: Unfortunately, Netlify's simple redirects don't support custom headers. The geolocation will be based on Netlify's edge server location. For accurate geolocation, consider:

  1. Using Netlify Functions (Edge Functions) - more complex setup
  2. Using Cloudflare Workers in front of Netlify

Advanced: Using Netlify Edge Functions

For more control (including IP forwarding), you can use Netlify Edge Functions:

netlify/edge-functions/zenovay-proxy.tsTypeScript
import type { Context } from '@netlify/edge-functions'

export default async (request: Request, context: Context) => {
const url = new URL(request.url)
const path = url.pathname.replace('/api/_z/', '')
const targetUrl = `https://api.zenovay.com/fp/${path}${url.search}`

// Get client IP
const clientIP = context.ip || ''

const headers = new Headers(request.headers)
headers.set('X-Zenovay-Real-IP', clientIP)
headers.delete('Host')

const response = await fetch(targetUrl, {
  method: request.method,
  headers: headers,
  body: request.body,
})

const responseHeaders = new Headers(response.headers)
responseHeaders.set('Access-Control-Allow-Origin', '*')

return new Response(response.body, {
  status: response.status,
  headers: responseHeaders,
})
}

export const config = {
path: '/api/_z/*',
}

Complete Example

Project StructureTEXT
my-netlify-site/
├── netlify.toml
├── public/
│   ├── _redirects
│   └── index.html
├── src/
│   └── ...
└── package.json
netlify.tomlTOML
[build]
command = "npm run build"
publish = "dist"

[[redirects]]
from = "/api/_z/*"
to = "https://api.zenovay.com/fp/:splat"
status = 200
force = true
public/index.htmlHTML
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>My Site</title>
  <script defer data-tracking-code="YOUR_TRACKING_CODE" src="/api/_z/script.js"></script>
</head>
<body>
  <div id="app"></div>
</body>
</html>

Final Checklist

Before you're done, verify ALL of these:

  • _redirects or netlify.toml has the Zenovay redirect rule
  • Redirect rule comes BEFORE any SPA catch-all rules
  • 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?