Suivi propriétaire avec Cloudflare Pages
Configurez un proxy propriétaire à l'aide des fonctions Cloudflare Pages. C'est la meilleure option si vous hébergez déjà votre site sur Cloudflare Pages.
Difficulté : Facile - Il suffit d'ajouter un fichier à votre projet. Cela prend environ 5 minutes.
Pourquoi cela fonctionne (même origine)
Cette approche utilise /api/_z/script.js qui se trouve sur la même origine que votre site Web. Firefox ETP et autres protections de suivi ne bloquent que les requêtes inter-origines.
- Le navigateur voit :
votredomaine.com/api/_z/script.js→ Même origine ✅ - Votre fonction fait un proxy vers le serveur Zenovay de serveur à serveur (le navigateur ne voit jamais ceci)
- Toutes les protections de suivi sont contournées car la requête est propriétaire
Avant de commencer
Assurez-vous d'avoir :
- Un projet déployé sur Cloudflare Pages
- Votre code de suivi Zenovay (trouvé dans Domaines, ouvrez votre site, puis l'onglet Général — dans la carte de script de suivi)
- Un accès au code source de votre projet
Format de votre code de suivi
Votre code de suivi ressemble à : ZV_XXXXXXXXXX
- Commence par
ZV_ - Suivi de 10 caractères (lettres et chiffres)
- SENSIBLE À LA CASSE - copiez-le exactement
- Exemple :
ZV_Q8U0GYD70WR

Fonctionnement
Lorsque vous ajoutez un dossier functions à votre projet Cloudflare Pages, Cloudflare crée automatiquement des fonctions sans serveur. Nous allons créer une fonction qui fait un proxy des requêtes vers l'API de Zenovay, ce qui les fait apparaître comme des requêtes propriétaires aux navigateurs.
Navigateur → votredomaine.pages.dev/api/_z/script.js ✅ (Propriétaire)
↓
Fonction Cloudflare
↓
api.zenovay.com/fp/script.js (Serveur à serveur)
Étape 1 : Créer le répertoire des fonctions
L'emplacement du fichier est CRITIQUE
Le dossier functions DOIT être à la racine de votre projet. Pas à l'intérieur de src/, pas à l'intérieur de public/.
Emplacement correct :
votre-projet/
├── functions/ ← ICI (racine du projet)
│ └── api/
│ └── _z/
│ └── [[path]].ts
├── src/
├── public/
└── package.json
MAUVAIS emplacements :
- ❌
src/functions/api/_z/[[path]].ts- Les fonctions ne fonctionneront pas - ❌
public/functions/api/_z/[[path]].ts- Les fonctions ne fonctionneront pas
Créez la structure de répertoires :
# Créer la structure de répertoires des fonctions
mkdir -p functions/api/_z
# Vérifiez que vous êtes au bon endroit
ls -la
# Vous devriez voir : functions/ src/ public/ package.json etc.Après avoir exécuté ces commandes, la structure de votre projet devrait ressembler à ceci :
votre-projet/
├── functions/
│ └── api/
│ └── _z/
│ └── [[path]].ts ← Nous allons créer ceci ensuite
├── src/
├── public/
├── package.json
└── ...
Étape 2 : Créer la fonction proxy
Le nommage du fichier est IMPORTANT
Le fichier DOIT être nommé [[path]].ts (ou [[path]].js pour JavaScript) :
- Deux crochets ouvrants :
[[ - Le mot
path - Deux crochets fermants :
]] - Extension de fichier :
.tsou.js
Cela crée une route « fourre-tout » qui gère tout chemin après /api/_z/.
TypeScript ou JavaScript ?
- Utilisez
.tssi votre projet utilise TypeScript - Utilisez
.jssi votre projet utilise JavaScript - Les deux fonctionnent exactement de la même manière
Si vous n'êtes pas sûr, essayez d'abord .ts. Si vous recevez des erreurs TypeScript lors de la compilation, renommez-le en .js.
Créez le fichier functions/api/_z/[[path]].ts avec ce contenu :
/**
* Zenovay First-Party Proxy
* This function proxies tracking requests to make them first-party
*/
interface EventContext {
request: Request
params: { path?: string[] }
}
export async function onRequest(context: EventContext): Promise<Response> {
const { request, params } = context
// Build path from catch-all parameter
// [[path]] captures: script.js, e/CODE, settings/CODE, etc.
const path = (params.path || []).join('/')
const url = new URL(request.url)
const targetUrl = `https://api.zenovay.com/fp/${path}${url.search}`
// Handle CORS preflight requests
if (request.method === 'OPTIONS') {
return new Response(null, {
status: 204,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
},
})
}
try {
// Get the real client IP for accurate geolocation
const clientIP = request.headers.get('CF-Connecting-IP') || ''
// Forward request with real IP
const proxyHeaders = new Headers(request.headers)
proxyHeaders.set('X-Zenovay-Real-IP', clientIP)
proxyHeaders.delete('Host')
// Build the request
const requestInit: RequestInit = {
method: request.method,
headers: proxyHeaders,
}
// Include body for POST/PUT/PATCH requests
if (['POST', 'PUT', 'PATCH'].includes(request.method)) {
requestInit.body = await request.arrayBuffer()
}
// Forward to Zenovay
const response = await fetch(targetUrl, requestInit)
// Add CORS headers to response
const responseHeaders = new Headers(response.headers)
responseHeaders.set('Access-Control-Allow-Origin', '*')
return new Response(response.body, {
status: response.status,
headers: responseHeaders,
})
} catch (error) {
console.error('Proxy error:', error)
return new Response(JSON.stringify({ error: 'Proxy error' }), {
status: 502,
headers: { 'Content-Type': 'application/json' },
})
}
}Alternative JavaScript
Si vous préférez JavaScript (pas de TypeScript), créez functions/api/_z/[[path]].js :
/**
* Zenovay First-Party Proxy
* This function proxies tracking requests to make them first-party
*/
export async function onRequest(context) {
const { request, params } = context
// Build path from catch-all parameter
const path = (params.path || []).join('/')
const url = new URL(request.url)
const targetUrl = `https://api.zenovay.com/fp/${path}${url.search}`
// Handle CORS preflight requests
if (request.method === 'OPTIONS') {
return new Response(null, {
status: 204,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
},
})
}
try {
// Get the real client IP for accurate geolocation
const clientIP = request.headers.get('CF-Connecting-IP') || ''
// Forward request with real IP
const headers = new Headers(request.headers)
headers.set('X-Zenovay-Real-IP', clientIP)
headers.delete('Host')
// Forward to Zenovay
const response = await fetch(targetUrl, {
method: request.method,
headers: headers,
body: ['POST', 'PUT', 'PATCH'].includes(request.method)
? await request.arrayBuffer()
: undefined,
})
// Add CORS headers to response
const responseHeaders = new Headers(response.headers)
responseHeaders.set('Access-Control-Allow-Origin', '*')
return new Response(response.body, {
status: response.status,
headers: responseHeaders,
})
} catch (error) {
return new Response(JSON.stringify({ error: 'Proxy error' }), {
status: 502,
headers: { 'Content-Type': 'application/json' },
})
}
}Étape 3 : Ajouter le script de suivi
Ajoutez ce script au code HTML de votre site Web. L'emplacement dépend de votre framework :
<!-- Zenovay Analytics - First-Party Tracking -->
<script defer
data-tracking-code="YOUR_TRACKING_CODE"
src="/api/_z/script.js">
</script>Remplacez YOUR_TRACKING_CODE par votre code de suivi réel depuis le tableau de bord Zenovay (par exemple, ZV_Q8U0GYD70WR).
Exemples spécifiques à chaque framework
React / Next.js
export default function RootLayout({ children }) {
return (
<html lang="en">
<head>
<script
defer
data-tracking-code="YOUR_TRACKING_CODE"
src="/api/_z/script.js"
/>
</head>
<body>{children}</body>
</html>
)
}Vue / Nuxt
<!-- In app.vue -->
<script setup>
useHead({
script: [
{
src: '/api/_z/script.js',
defer: true,
'data-tracking-code': 'YOUR_TRACKING_CODE'
}
]
})
</script>Astro
---
const { title } = Astro.props;
---
<html lang="en">
<head>
<title>{title}</title>
<script defer data-tracking-code="YOUR_TRACKING_CODE" src="/api/_z/script.js"></script>
</head>
<body>
<slot />
</body>
</html>HTML simple
<!DOCTYPE html>
<html>
<head>
<title>My Website</title>
<script defer data-tracking-code="YOUR_TRACKING_CODE" src="/api/_z/script.js"></script>
</head>
<body>
<!-- Your content -->
</body>
</html>Étape 4 : Déployer
Déployez votre projet sur Cloudflare Pages :
Option A : Utiliser Git (Recommandé)
Si vous avez des déploiements automatiques à partir de Git :
git add .
git commit -m "Add Zenovay first-party tracking"
git pushCloudflare Pages compilera et déploiera automatiquement.
Option B : Utiliser la CLI Wrangler
# Compilez d'abord votre projet
npm run build
# Déployez sur Cloudflare Pages
npx wrangler pages deploy ./dist --project-name=your-project-nameRemplacez ./dist par votre répertoire de sortie de compilation (pourrait être ./out, ./build, etc.) et your-project-name par le nom de votre projet Cloudflare Pages.
Étape 5 : Vérifier que cela fonctionne
Vérification 1 : Onglet Fonctions dans Cloudflare
- Allez à Cloudflare Dashboard → Pages
- Cliquez sur votre projet
- Cliquez sur l'onglet Functions
- Vous devriez voir
api/_z/[[path]]listé
Vous ne voyez pas la fonction ?
- Assurez-vous que le dossier
functions/est à la racine du projet (pas à l'intérieur desrc/) - Assurez-vous que le fichier est nommé
[[path]].tsou[[path]].js - Vérifiez que le déploiement s'est terminé avec succès
Vérification 2 : Onglet Réseau dans le navigateur
- Ouvrez votre site déployé
- Appuyez sur F12 (ou Cmd+Option+I sur Mac) pour ouvrir les outils de développement
- Cliquez sur l'onglet Network
- Actualisez la page (Cmd+R ou Ctrl+R)
- Dans la zone de filtre, tapez
script.js - Recherchez
/api/_z/script.js
Ce que vous devriez voir :
- Statut : 200 ✅
- Domaine : Votre domaine Pages (par exemple,
votre-projet.pages.dev) - Réponse : Code JavaScript
Vérification 3 : Mode strict Firefox (Plus important !)
Firefox a la protection de suivi la plus stricte. Si cela fonctionne dans Firefox, cela fonctionne partout.
- Ouvrez le navigateur Firefox
- Cliquez sur le menu (☰) → Paramètres
- Cliquez sur Confidentialité et sécurité dans la barre latérale gauche
- Sous « Protection renforcée contre le suivi », sélectionnez Strict
- Visitez votre site Web
- Ouvrez les outils de développement (F12) → onglet Réseau
- Actualisez et vérifiez que
/api/_z/script.jsse charge avec le statut 200
Vérification 4 : Tableau de bord Zenovay
- Allez à app.zenovay.com et connectez-vous
- Cliquez sur votre site Web
- Visitez votre site déployé dans un autre onglet
- Dans 1-2 minutes, vous devriez voir la visite apparaître dans votre tableau de bord
Liste de vérification finale
Avant de terminer, vérifiez TOUS les éléments suivants :
- Le fichier
functions/api/_z/[[path]].tsexiste à la racine du projet - La fonction apparaît dans l'onglet Fonctions de Cloudflare Pages
- Le script se charge à
/api/_z/script.jsavec le statut 200 - L'attribut
data-tracking-codecontient votre code de suivi correct - Testé dans Firefox avec la protection renforcée contre le suivi définie sur Strict
- Les visites apparaissent dans le tableau de bord Zenovay
Dépannage
La fonction retourne 404
Cause : Le fichier de fonction est au mauvais emplacement ou a le mauvais nom.
Solution :
- Vérifiez que le fichier se trouve exactement à
functions/api/_z/[[path]].ts- Le dossier
functionsdoit être à la racine du projet - Doit avoir les sous-répertoires
api/_z/ - Doit être nommé
[[path]].tsavec des crochets doubles
- Le dossier
- Assurez-vous que le dossier
functionsest inclus dans votre déploiement - Vérifiez l'onglet Fonctions du tableau de bord Cloudflare
La fonction retourne 500
Cause : Il y a une erreur de syntaxe ou une erreur d'exécution dans le code de la fonction.
Solution :
- Vérifiez les journaux des fonctions dans Cloudflare pour plus de détails sur les erreurs :
- Allez à votre projet Pages → Functions → Logs
- Vérifiez que tout le code a été copié correctement
- Assurez-vous que vous exportez
onRequest(pasexport default)
Erreurs TypeScript pendant la compilation
Cause : Définitions de type manquantes.
Solution : Option 1 : Installez les types Cloudflare Workers :
npm install --save-dev @cloudflare/workers-typesOption 2 : Utilisez la version JavaScript à la place (renommez en [[path]].js)
Erreurs CORS dans la console
Cause : Les en-têtes CORS ne sont pas ajoutés correctement.
Solution : Assurez-vous que votre fonction inclut les deux éléments suivants :
- Le gestionnaire OPTIONS pour les requêtes de préalable
responseHeaders.set('Access-Control-Allow-Origin', '*')dans la réponse
La géolocalisation est incorrecte
Cause : L'IP client n'est pas transmise.
Solution : Vérifiez que votre fonction inclut :
const clientIP = request.headers.get('CF-Connecting-IP') || ''
proxyHeaders.set('X-Zenovay-Real-IP', clientIP)
Le script se charge mais aucune donnée dans le tableau de bord
Cause : Décalage du code de suivi.
Solution :
- Ouvrez la console des outils de développement (F12 → onglet Console) et recherchez les erreurs
- Vérifiez que votre
data-tracking-codecorrespond exactement à ce qui se trouve dans votre tableau de bord Zenovay (sensible à la casse !) - Assurez-vous que le domaine est enregistré dans Zenovay
Prochaines étapes
- Événements personnalisés - Suivre les interactions des utilisateurs
- Identification du visiteur - Lier l'analyse aux utilisateurs
- Dépannage - Plus d'aide en cas de problème