Proxying Plausible in Remix

Roger Stringer
September 13, 2023
5 min read
Proxying Plausible in Remix

I have been using Plausible since mid-2021 and really enjoy it. Plausible is a more privacy-oriented alternative to Google Analytics and I use it for all my projects, including this site.

Adding Plausible to any site is easy with just one line of code:

html
1<script async defer data-domain="domain.com" src="https://plausible.io/js/script.js" />
2

Unfortunately, Plausible can be blocked by some blocklist maintainers who favor blocking every tracking code irrespective of its nature, be it good or bad.

You can find more details on Plausible's site. The main takeaway is that Plausible is a more privacy-sensitive alternative to Google Analytics, but it pays the price because of blocklist maintainers who intend to block all tracking scripts.

For Next.js apps, there's Next-Plausible with proxying support. However, for Remix apps, a different approach is required.

We could download the file from https://plausible.io/js/script.js, place it in the public folder, reference it in root.tsx, and it would work, but there are two downsides:

  1. You might miss future script.js updates, which implies constantly downloading the script.
  2. You'll still need to figure out how to proxy calls to https://plausible.io/api/event.

Luckily, Remix's resource routes can solve this issue, as it allows us to proxy both the script and the requests to /api/event from a loader function within a route.

//

1. Script Proxying

Start by creating a file at app/routes/js_script[.js].ts. Add the [.js] part to the file name so that Remix serves the file at mysite.com/js/script.js:

// app/routes/js.script[.js].ts:
1export const loader = async () => {
2  const response = await fetch('https://plausible.io/js/script.js');
3  const script = await response.text();
4  const { status, headers } = response;
5
6  return new Response(script, {
7    status,
8    headers,
9  });
10};
11

This piece of code does the following:

  1. Creates a loader function that fetches the script.js file from Plausible's servers and sends it back to the browser.
  2. Extracts the file content (via await response.text();) and passes it as the new body.
  3. Also forwards the headers and status code from the request to Plausible's servers.

2. Event Proxying

We still need to proxy calls to https://plausible.io/api/event. For this, create a route at app/routes/api.event.ts. Plausible has this URL coded in their script.js, which is why we use it in our app. Here's what should be in this file:

// app/routes/api.event.ts:
1import type { DataFunctionArgs } from '@remix-run/node'; // or whatever runtime you are using...
2
3export const action = async ({ request }: DataFunctionArgs) => {
4  const { method, body } = request;
5
6  const response = await fetch('https://plausible.io/api/event', {
7    body,
8    method,
9    headers: {
10      'Content-Type': 'application/json',
11    },
12  });
13
14  const responseBody = await response.text();
15  const { status, headers } = response;
16
17  return new Response(responseBody, {
18    status,
19    headers,
20  });
21};
22

This route consists of:

  1. An action function called by script.js as a POST request. It takes the method and body from the request and performs a fetch to Plausible's servers, passing along the method, body and a Content-Type: 'application/json' header.
  2. Upon return, it converts the reponse body to a string and creates a new response which then passes the proxied response status code, headers, and body back to our app.

This direct proxying ensures that whatever Plausible's servers return, Plausible's script will process it.

3. Referring to the Script File

To finish, update root.tsx by adding the following line inside the <head></head> tags:

// root.tsx:
1<script defer data-domain="yourdomain.com" src="/js/script.js" />
2

Here's what will happen:

  1. Remix fetches the script file from your resource route, then grabs the script file from Plausible's server.
  2. The script file is sent to the browser, which loads and parses it (deferred, so it doesn't halt the rest of document load).
  3. The script file makes a request to /api/event, which is also proxied by Remix and is forwarded to Plausible's API.
//

As an added note, if you want the event route in a different location than /api/event, you can add a data-api attribute to the Plausible call. For instance, if we decide to route event at /r2d2/event:

html
1<script defer data-domain="yourdomain.com" data-api="/r2d2/event" src="/js/script.js" />
2

You can adopt this process to proxy other requests as well, not just Plausible.

Do you like my content?

Sponsor Me On Github