Aller au contenu

Proxy first-party (anti-adblock)

Les domaines d’analytics connus finissent dans EasyPrivacy : 25 à 40 % des visiteurs tech sont invisibles sans proxy. La parade : servir le script et poster les événements depuis le domaine du site suivi, qui reverse-proxie vers Elvn. Quasi imblocable sans casser le site.

Deux réécritures, avec des noms neutres (/stats/… par convention — n’importe quel path neutre convient, éviter track, analytics, collect) :

GET /stats/t.js -> https://elvn.live/js/t.js
POST /stats/e -> https://elvn.live/api/e

Snippet correspondant (le dashboard le génère dans Settings → Tracking, onglet « proxy ») :

<script defer src="/stats/t.js" data-site="SITE_PUBLIC_ID" data-api="/stats/e"></script>

Deux exigences côté proxy :

  1. Transmettre l’en-tête Origin de la requête entrante (comportement par défaut de tous les proxys ci-dessous) : le tracker envoie u en path relatif, le Worker valide alors l’origine via ce header contre le domaine déclaré du site.
  2. Transmettre le User-Agent entrant (défaut partout) : il sert au hash visiteur et aux dimensions navigateur/OS/appareil.

Le Worker de collecte lit l’IP de connexion et la géolocalisation Cloudflare de la requête qui lui parvient. Derrière un proxy serveur, c’est l’IP du serveur du site :

  • les visiteurs uniques sont dégradés (tous les visiteurs partagent l’IP du proxy : ils ne sont plus distingués que par leur User-Agent) ;
  • la géolocalisation affichée est celle du serveur, pas du visiteur ;
  • le rate limiting (par site + IP) s’applique à l’IP du proxy, donc à tout le trafic proxifié du site.

Aucun en-tête X-Forwarded-For n’est lu (il serait falsifiable sans mécanisme de confiance). Le proxy first-party est donc un compromis : plus de couverture, moins de précision uniques/geo. La variante « Worker route sur la zone du site » a exactement la même limite.

location = /stats/t.js {
proxy_pass https://elvn.live/js/t.js;
proxy_set_header Host elvn.live;
proxy_ssl_server_name on;
proxy_ssl_name elvn.live;
# cache local optionnel : le script est déjà servi avec max-age=86400 + ETag
}
location = /stats/e {
proxy_pass https://elvn.live/api/e;
proxy_set_header Host elvn.live;
proxy_ssl_server_name on;
proxy_ssl_name elvn.live;
}

Origin et User-Agent sont transmis par défaut (nginx ne réécrit que Host et Connection).

next.config.js (ou .mjs/.ts) :

/** @type {import('next').NextConfig} */
const nextConfig = {
async rewrites() {
return [
{ source: "/stats/t.js", destination: "https://elvn.live/js/t.js" },
{ source: "/stats/e", destination: "https://elvn.live/api/e" },
];
},
};
module.exports = nextConfig;

Puis le snippet proxy dans le layout :

<Script defer src="/stats/t.js" data-site="SITE_PUBLIC_ID" data-api="/stats/e" />

(ou une balise <script> classique dans app/layout.tsx / _document.tsx.)

Variante Worker route (site déjà sur une zone Cloudflare)

Section intitulée « Variante Worker route (site déjà sur une zone Cloudflare) »

Un petit Worker déployé sur la zone du site suivi, avec la route www.exemple.fr/stats/* :

// stats-proxy — Worker sur la zone du site suivi
export default {
async fetch(request) {
const url = new URL(request.url);
if (request.method === "GET" && url.pathname === "/stats/t.js") {
return fetch("https://elvn.live/js/t.js", {
headers: { "User-Agent": request.headers.get("User-Agent") ?? "" },
cf: { cacheTtl: 86400, cacheEverything: true },
});
}
if (request.method === "POST" && url.pathname === "/stats/e") {
// Transmet le body et les en-têtes entrants (Origin, User-Agent, Sec-GPC…)
return fetch("https://elvn.live/api/e", {
method: "POST",
headers: request.headers,
body: request.body,
});
}
return new Response(null, { status: 404 });
},
};

wrangler.jsonc du proxy :

{
"name": "stats-proxy",
"main": "src/index.js",
"compatibility_date": "2026-07-01",
"routes": [{ "pattern": "www.exemple.fr/stats/*", "zone_name": "exemple.fr" }],
}
exemple.fr {
handle /stats/t.js {
rewrite * /js/t.js
reverse_proxy https://elvn.live {
header_up Host elvn.live
}
}
handle /stats/e {
rewrite * /api/e
reverse_proxy https://elvn.live {
header_up Host elvn.live
}
}
# ... reste du site
}
Fenêtre de terminal
# le script doit revenir en 200 avec un ETag
curl -sI https://www.exemple.fr/stats/t.js | head -5
# l'endpoint doit répondre 202 (même pour un payload vide : réponse silencieuse par design)
curl -s -o /dev/null -w "%{http_code}\n" -X POST https://www.exemple.fr/stats/e --data '{}'

Puis visiter le site : la page Settings → Tracking du dashboard passe au vert à la réception du premier événement.