DocsInstall

Count visitors who block analytics

Serve Nodium's script from your own domain so visitors whose ad blockers stop analytics are counted too: WordPress, Cloudflare, Vercel, Netlify and Nginx.

Why some visits aren't counted without one

Some ad blockers stop scripts from analytics services by their address, Nodium's among them. A visit the script never reached isn't counted. A proxy serves the script and passes its events on from a path on your own domain, so a blocker sees your site, not Nodium, and those visits count too.

Nodium still sends nothing when a browser asks not to be tracked (Do Not Track or Global Privacy Control), proxy or not. Nothing is stored on your visitors' devices either way.

Your Nodium key

Through a proxy, the visitor's address reaches Nodium from your server, which signs it with your Nodium key so nobody else can send visits as your proxy. The key, your path and the tag to use are in Settings → Site → Proxy, filled in; below, /abc123 and YOUR-SITE-KEY stand for yours. The address is used as a direct visit's is and never kept.

WordPress

  1. Nodium Analytics, the plugin that does this for you, isn't in WordPress's plugin directory yet.
  2. Until then, if your site is on Cloudflare, follow the Cloudflare steps.

Cloudflare

  1. In Cloudflare, open Workers & Pages, then Create, and create a Worker from Hello World.
  2. Edit its code: replace it with the code below, and deploy.
  3. In the Worker's Settings, under Variables and Secrets, add a secret named NODIUM_KEY holding your Nodium key.
  4. Under Domains & Routes, add the route yourdomain.com/abc123/* on your site's zone, with your own domain in place of yourdomain.com.
  5. Replace the Nodium snippet in your pages' <head> with this tag: <script defer data-site="YOUR-SITE-KEY" data-api="/abc123/e" src="/abc123/s.js"></script>

The Worker signs each visitor's address with your key, so nothing else needs turning on.

// Nodium's proxy on your own domain (usenodium.com/docs/proxy). NODIUM_KEY is a secret.
const PATH = "/abc123";
const CDN = "https://cdn.usenodium.com";

const b64url = (bytes) =>
  btoa(String.fromCharCode(...bytes)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");

async function forward(request, env, url) {
  const done = new Response(null, { status: 204 });
  const [token, secret] = (env.NODIUM_KEY ?? "").trim().slice(3).split(".");
  const ip = request.headers.get("cf-connecting-ip") ?? "";
  if (!token || !secret || !ip) return done;
  const ts = Math.floor(Date.now() / 1000);
  const enc = new TextEncoder();
  const key = await crypto.subtle.importKey("raw", enc.encode(secret), { name: "HMAC", hash: "SHA-256" }, false, ["sign"]);
  const sig = new Uint8Array(await crypto.subtle.sign("HMAC", key, enc.encode(`${ip}|${ts}|${token}`)));
  await fetch(CDN + "/api/v", {
    method: "POST",
    body: await request.text(),
    headers: {
      "content-type": "text/plain",
      origin: request.headers.get("origin") ?? url.origin,
      "user-agent": request.headers.get("user-agent") ?? "",
      "x-nodium-forward": `${ip};${ts};${b64url(sig)}`,
    },
  });
  return done;
}

export default {
  async fetch(request, env, ctx) {
    const url = new URL(request.url);
    if (url.pathname === PATH + "/s.js") {
      const cached = await caches.default.match(request);
      if (cached) return cached;
      const script = await fetch(CDN + "/n.js");
      // A failed fetch passes through as it is, and isn't kept.
      if (!script.ok) return new Response(null, { status: 503 });
      const res = new Response(script.body, {
        headers: { "content-type": "application/javascript", "cache-control": "public, max-age=3600" },
      });
      ctx.waitUntil(caches.default.put(request, res.clone()));
      return res;
    }
    if (url.pathname === PATH + "/e" && request.method === "POST") return forward(request, env, url);
    return fetch(request);
  },
};

Vercel

  1. Add these rewrites to vercel.json at your project's root, and deploy.
  2. Replace the Nodium snippet in your pages' <head> with this tag: <script defer data-site="YOUR-SITE-KEY" data-api="/abc123/e" src="/abc123/s.js"></script>
  3. Turn on Accept addresses forwarded by my server, below.

If vercel.json already has rewrites, add these two to its list.

{
  "rewrites": [
    {
      "source": "/abc123/s.js",
      "destination": "https://cdn.usenodium.com/n.js"
    },
    {
      "source": "/abc123/e",
      "destination": "https://cdn.usenodium.com/api/v"
    }
  ]
}

Netlify

  1. Add these lines to the top of _redirects in your publish folder, and deploy.
  2. Replace the Nodium snippet in your pages' <head> with this tag: <script defer data-site="YOUR-SITE-KEY" data-api="/abc123/e" src="/abc123/s.js"></script>
  3. Turn on Accept addresses forwarded by my server, below.
/abc123/s.js  https://cdn.usenodium.com/n.js  200
/abc123/e  https://cdn.usenodium.com/api/v  200

Nginx

  1. Add these two locations to your site's server block, and reload Nginx.
  2. Replace the Nodium snippet in your pages' <head> with this tag: <script defer data-site="YOUR-SITE-KEY" data-api="/abc123/e" src="/abc123/s.js"></script>
  3. Turn on Accept addresses forwarded by my server, below.

It passes on the address that reached Nginx. If Nginx sits behind Cloudflare or another proxy, set its real_ip module to take the visitor's address from that proxy first.

location = /abc123/s.js {
    proxy_pass https://cdn.usenodium.com/n.js;
    proxy_set_header Host cdn.usenodium.com;
    proxy_ssl_server_name on;
}
location = /abc123/e {
    proxy_pass https://cdn.usenodium.com/api/v;
    proxy_set_header Host cdn.usenodium.com;
    proxy_set_header X-Forwarded-For $remote_addr;
    proxy_ssl_server_name on;
}

Servers that can't sign

Vercel, Netlify and Nginx pass each visitor's address on without a signature. With Accept addresses forwarded by my server on, Nodium trusts the first address they forward, as Plausible does for every site. Anyone who knows your site key could then send visits that look like they came from other addresses, so it's off unless you turn it on.

Places through a proxy

Cloudflare places a direct visit; through a proxy, it would see your server, so Nodium places the visit from IP Geolocation by DB-IP instead. The two can name the same region or city differently, so a place can show as two rows for the days around a move to a proxy. How it's counted.