Faster Cloudflare Worker for Plausible Analytics | ~Zaraz

Shave 100ms off the Plausible Analytics POST response, making it as fast as Cloudflare Zaraz GA4

I moved over to Plausible Analytics; I had been using JetPack Stats. But I’ve never been happy with the (1) extra 3rd party DNS lookup, (2) large JavaScript payload, (3) cookies, and (4) sending data to a 3rd party.

Cloudflare has an excellent solution for Google Analytics GA4 by proxying requests through Zaraz. I tried it. It is fast and easy to implement. But GA4 still sets cookies and sends data to a 3rd party, which didn’t quite meet my goals.

I figure I could use Zaraz with self-hosted Plausible Analytics. Plausible is a web analytics tool with privacy in mind. I never complied with the EU cookie consent banner anyway (I’m not in the EU), but I wish sites all would all go cookie-free. Not because I don’t like cookies. 🍪 I do. But so they can get rid of that cookie consnet banner!

So, Plausible doesn’t support Cloudflare Zaraz. But there is a workaround to proxy Plausible through Cloudflare using Cloudflare Workers from the Plausible documentation. This saves an extra DNS lookup. But the problem is it’s still waiting on the origin server. I tested ~140ms from Eastern US.

Plausible Cloudflare Worker

Now, it’s not “render blocking” so this isn’t horrible, but it’s the slowest item on my site. Visitors from US West get it fast, but it’s 100-200ms from US East and probably around 500-600ms from outside North America.

But, we can make the Plausible Worker as fast as the Zaraz GA4 solution.

The problem is the communication goes like this:

async function postData(event) {
    const request = new Request(event.request);
    request.headers.delete('cookie');
    return await fetch("https://plausible.io/api/event", request);
}
  1. 👩‍💻 Client → CF Worker: Here’s the POST data. Waiting for a response.
  2. ☁️ CF Worker → Origin: Here’s the POST data. Waiting for a response.
  3. 💾 Origin → CF Worker: “202: ok”
  4. ☁️ CF Worker → Client: “202: ok” (143ms response time)

But we only care about getting the POST data; the response doesn’t matter. We can change the worker to send a 202 before it knows the response from the origin server.

I thought I’d rewrite the function ask ChatGPT to do it for me.

(gave it original worker script)

ChatGPT made one trivial mistake; the return status should be 202.

async function postData(event) {
    const request = new Request(event.request);
    request.headers.delete('cookie');

    const response = new Response('OK', { status: 202 });

    event.waitUntil(async function () {
        await fetch("https://plausible.io/api/event", request);
    }());

    return response;
}
  1. 👩‍💻 Client → CF Worker: Here’s the POST data. Waiting for a response.
  2. ☁️ CF Worker → Client: “202: ok” (9ms response time)
  3. ☁️ CF Worker → Origin: Here’s the POST data. Waiting for a response.
  4. 💾 Origin → CF Worker: “202: ok”
Asynchronous Response Plausible Cloudflare Worker

Essentially, a Cloudflare Edge location can respond in 9ms instead of waiting 143ms to go all the way to Sandpoint and hit the origin server.

Here’s the complete modified Cloudflare Worker Script:

const ScriptName = '/js/script.js';
const Endpoint = '/api/event';

const ScriptWithoutExtension = ScriptName.replace('.js', '')

addEventListener('fetch', event => {
    event.passThroughOnException();
    event.respondWith(handleRequest(event));
})

async function handleRequest(event) {
  const pathname = new URL(event.request.url).pathname
  const [baseUri, ...extensions] = pathname.split('.')

  if (baseUri.endsWith(ScriptWithoutExtension)) {
      return getScript(event, extensions)
  } else if (pathname.endsWith(Endpoint)) {
      return postData(event)
  }
  return new Response(null, { status: 404 })
}

async function getScript(event, extensions) {
    let response = await caches.default.match(event.request);
    if (!response) {
        response = await fetch("https://plausible.io/js/plausible." + extensions.join("."));
        event.waitUntil(caches.default.put(event.request, response.clone()));
    }
    return response;
}

async function postData(event) {
    const request = new Request(event.request);
    request.headers.delete('cookie');

    const response = new Response('OK', { status: 202 });

    event.waitUntil(async function () {
        await fetch("https://plausible.io/api/event", request);
    }());

    return response;
}

I can’t think of any downside to this. If the POST is not successful, the client isn’t going to care anyway.

A millisecond is worth a fortune. — Eric Kirzner

3 thoughts on “Faster Cloudflare Worker for Plausible Analytics | ~Zaraz”

  1. Thanks a lot for this guide. I try to achieve the same for my self hosted plausible instance. However, I always get 404 not found if I test the worker script. I tried exactly 1:1 the script you posted, which should work since it uses the official plausible url ? I also tried the code on the plausible docs…. any idea why that’s the case?

    Reply
    • Hi, Ars. I just looked at your site, I didn’t get a 404, but you may have turned it off since you posted. Does the official worker script work fine for you?

      On Cloudflare, did you set up a worker route for your domain? Under worker routes –> HTTP routes –> route: I set mine to *b3n.org/p/* since I’m calling: const ScriptName = ‘/p/eye.js’;

      Reply

Leave a Comment