Ad blockers block analytics. That’s their job. But it means I was missing 30-40% of my traffic data.
I set up a first-party proxy that routes PostHog through my own domain. Ad blockers don’t block first-party requests.
The problem
PostHog’s default setup loads from us.i.posthog.com. Ad blockers have this domain in their blocklists:
||posthog.com^
||i.posthog.com^
||us.i.posthog.com^
Users with ad blockers (30-40% of tech-savvy audiences) send no analytics. I had no idea which fonts they searched for or which pages they visited.
The solution
Route PostHog through a subdomain I control:
e.fontalternatives.com → eu.i.posthog.com
The e is short for “events”. It looks like a first-party endpoint. Ad blockers don’t block it because it’s not on their lists.
EU data residency
PostHog offers EU hosting at eu.i.posthog.com. Since FontAlternatives serves a global audience and I’m EU-based, I use the EU endpoint for GDPR compliance.
The proxy preserves this: requests go through my domain but ultimately hit PostHog’s EU servers.
The Worker
The proxy is a simple Cloudflare Worker:
export default {
async fetch(request: Request): Promise<Response> {
const url = new URL(request.url);
// Rewrite to PostHog EU endpoint
const posthogUrl = new URL(url.pathname + url.search, 'https://eu.i.posthog.com');
// Clone request with new URL
const posthogRequest = new Request(posthogUrl.toString(), {
method: request.method,
headers: request.headers,
body: request.body,
});
// Forward to PostHog
const response = await fetch(posthogRequest);
// Clone response with CORS headers
const newHeaders = new Headers(response.headers);
newHeaders.set('Access-Control-Allow-Origin', 'https://fontalternatives.com');
newHeaders.set('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
newHeaders.set('Access-Control-Allow-Headers', 'Content-Type');
return new Response(response.body, {
status: response.status,
headers: newHeaders,
});
},
};
It’s a transparent proxy. Receives request, forwards to PostHog, returns response.
Wrangler configuration
Separate config for the proxy worker:
{
"name": "fontalternatives-posthog",
"main": "src/workers/posthog-proxy.ts",
"compatibility_date": "2024-01-01",
"routes": [
{
"pattern": "e.fontalternatives.com/*",
"custom_domain": true
}
]
}
Deploy with:
wrangler deploy --config wrangler.posthog.jsonc
Client configuration
Update the PostHog client to use the proxy:
import posthog from 'posthog-js';
posthog.init('phc_your_project_key', {
api_host: 'https://e.fontalternatives.com',
ui_host: 'https://eu.posthog.com', // Dashboard still uses direct URL
});
The api_host is the proxy. The ui_host is where the PostHog dashboard lives (for session recordings, etc.).
Handling CORS
Browsers send preflight OPTIONS requests for cross-origin POST. The Worker handles these:
async fetch(request: Request): Promise<Response> {
// Handle preflight
if (request.method === 'OPTIONS') {
return new Response(null, {
headers: {
'Access-Control-Allow-Origin': 'https://fontalternatives.com',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
'Access-Control-Max-Age': '86400',
},
});
}
// ... rest of proxy logic
}
The Access-Control-Max-Age caches preflight responses for 24 hours, reducing request overhead.
What gets proxied
PostHog sends several types of requests:
| Endpoint | Purpose |
|---|---|
/capture | Event tracking |
/decide | Feature flags |
/engage | User properties |
/batch | Batched events |
/s/ | Session recordings |
The proxy forwards all of them transparently.
Privacy considerations
This proxy doesn’t change what data PostHog collects. It only changes the transport path.
I still:
- Don’t collect PII without consent
- Respect Do Not Track headers
- Provide opt-out mechanisms
- Comply with GDPR (EU data residency)
The proxy helps me understand aggregate behavior, not track individuals.
The impact
Before and after comparison over 30 days:
| Metric | Before proxy | After proxy | Change |
|---|---|---|---|
| Page views | 12,400 | 18,200 | +47% |
| Unique users | 4,100 | 5,800 | +41% |
| Search events | 2,800 | 4,100 | +46% |
The “missing” traffic was real users with ad blockers. Now I see them.
Deployment
I deploy the proxy separately from the main site:
# Main site
npm run deploy
# PostHog proxy
npm run deploy:posthog
Different workers, different domains, independent deployment.
DNS setup
Cloudflare DNS for the proxy subdomain:
e.fontalternatives.com CNAME fontalternatives-posthog.ruzicic.workers.dev
The CNAME points to the Worker. Cloudflare handles SSL automatically.
Tradeoffs
What I gained:
- 30-40% more analytics data
- Complete picture of user behavior
- Better font demand signals. This proxy powers the demand tracking system described in The Content Flywheel.
What I lost:
- Extra Worker to maintain
- Potential for proxy to go down (PostHog would too)
- Slight latency increase (extra hop)
Ethical considerations: Some argue that bypassing ad blockers disrespects user choice. I disagree for analytics specifically:
- Analytics don’t show ads
- Analytics help me improve the site
- Users blocking analytics often do so accidentally (browser extensions block everything)
- I don’t track individuals, only aggregate patterns
For ad-supported sites showing actual ads, the ethics are different.
Monitoring
I monitor the proxy with PostHog itself (ironic, I know):
// In the Worker
const start = Date.now();
const response = await fetch(posthogRequest);
const duration = Date.now() - start;
// Log to PostHog (through production, not proxy)
if (duration > 1000) {
console.log(`Slow PostHog proxy: ${duration}ms`);
}
If the proxy slows down or errors, I see it in PostHog’s server-side logs.
Code organization
The proxy lives in the main repo:
src/
workers/
posthog-proxy.ts # The proxy Worker
wrangler.posthog.jsonc # Proxy-specific config
Same codebase, different deployment target.
Alternative approaches
Other ways to proxy analytics:
Reverse proxy (nginx):
location /e/ {
proxy_pass https://eu.i.posthog.com/;
}
Works but requires a server. I don’t have one.
Edge functions (Vercel/Netlify): Similar to Workers but platform-specific. I’m already on Cloudflare.
PostHog’s reverse proxy feature: PostHog offers guidance on this. My approach follows their recommendations.
The result
FontAlternatives now captures analytics from all users, not just those without ad blockers. The font demand signals are more accurate, and I can make better decisions about which fonts to add.
Total cost: $0 (Cloudflare Workers free tier).