Skip to content

Send page views through langx.io, because an ad blocker drops posthog.com - #165

Closed
xuelink wants to merge 1 commit into
mainfrom
claude/posthog-reverse-proxy
Closed

Send page views through langx.io, because an ad blocker drops posthog.com#165
xuelink wants to merge 1 commit into
mainfrom
claude/posthog-reverse-proxy

Conversation

@xuelink

@xuelink xuelink commented Sep 8, 2026

Copy link
Copy Markdown
Member

PostHog's project health page had one warning — no reverse proxy — and it was the honest one: every request went to eu.i.posthog.com, which every ad blocker's list contains, so a visitor running one was never counted.

What changed

  • functions/ingest/[[path]].js — a Pages Function that forwards /ingest/* to PostHog's European cloud from Cloudflare's edge. /static/ and /array/ go to the asset host and are held in the edge cache; everything else goes to the API host as it arrived. cookie and authorization are dropped (this site sets no cookie, but Cloudflare's __cf_bm is set on langx.io and would otherwise ride along), and CF-Connecting-IP is passed on as X-Forwarded-For — cookieless counting is a hash of the address, so without it a whole country arrives as one visitor.
  • src/lib/analytics.tsapi_host is now https://langx.io/ingest, with ui_host set to https://eu.posthog.com so links and the toolbar still point at PostHog.
  • .github/workflows/deploy.yml — wrangler looks for a functions directory in its working directory, not inside the directory it publishes, so the artifact carries build/ and functions/ side by side and the publish step names build/ instead of the root. Wrangler's upload ignore list contains functions, so none of it is served as a static file.
  • Cookie policy 3.4 — it said the connection to PostHog was made from your browser directly rather than through us. That is no longer true, so it now says what happens, including that we pass your address on. The route changed; the recipient did not.

Testing

Run against a real workerd with wrangler pages dev:

  • GET /ingest/static/array.js → 200, PostHog's 288 KB array.js.
  • POST /ingest/i/v0/e/{"status":"Ok"} from PostHog.
  • Loading the site with the SDK pointed at the local proxy produced POST /ingest/e/ 200 OK on its own — end to end, without help.
  • Header handling checked by proxying to an echo server: a forged X-Forwarded-For is replaced by the Cloudflare address, cookie and authorization are gone, and method, body and query string arrive unchanged.
  • npm run check, npm run lint and vite build pass; site routes are untouched by the function.

The one thing that cannot be tested off main is the artifact layout in Actions — worth a look at the deploy log on the first run.

🤖 Generated with Claude Code

….com

PostHog's project health page had one warning, and it was the honest
one: without a reverse proxy every request goes to eu.i.posthog.com,
which is on the blocklist that every ad blocker ships with. A visitor
running one was not being counted at all, and no amount of care about
what we send changes a request that never leaves.

/ingest is now a Pages Function that forwards to PostHog's European
cloud from Cloudflare's edge: /static/ and /array/ to the asset host,
held in the edge cache because those files are public and identical for
everyone, and everything else to the API host as it arrived. The cookie
and authorization headers are dropped — this site writes no cookie of
its own, but Cloudflare's __cf_bm is set on langx.io and would otherwise
ride along to a third party. CF-Connecting-IP is passed on as
X-Forwarded-For, which matters more here than it looks: cookieless
counting is a hash of the address, so without it a whole country would
arrive at PostHog as one visitor.

The deploy had to learn the shape. Wrangler looks for a `functions`
directory in its working directory rather than inside the directory it
publishes, so the artifact now carries build/ and functions/ side by
side and the publish step names build/ instead of the root. Wrangler's
own upload ignore list contains `functions`, so nothing of it is served
as a static file.

Section 3.4 of the cookie policy said the connection to PostHog was made
from your browser directly rather than through us. That was true when it
was written and is not any more, so it now says what actually happens,
including that we pass your address on — the route changed, the
recipient did not.

Verified against a real workerd with `wrangler pages dev`: the asset
path returns PostHog's array.js, a POST to the capture endpoint comes
back Ok, and loading the site with the SDK pointed at the local proxy
produced POST /ingest/e/ on its own. Header handling was checked by
proxying to an echo server: a forged X-Forwarded-For is replaced by the
Cloudflare address, cookie and authorization are gone, and the method,
body and query string arrive unchanged. What cannot be tested off main
is the artifact layout in Actions, which is the one thing to watch on
the first deploy.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@xuelink

xuelink commented Sep 8, 2026

Copy link
Copy Markdown
Member Author

Duplicate of #164, which landed on main ~20 minutes before this branch was cut and is already deployed: https://langx.io/relay/static/array.js serves PostHog's array.js and the live bundle is built against /relay. Closing in favour of that one — no need for a second proxy on a different path.

@xuelink xuelink closed this Sep 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant