PostHog Analytics That Survive Ad Blockers With First-Party Proxying
How to proxy PostHog Cloud through your own domain in Next.js using rewrites to reduce blocked requests and improve performance.

If you rely on analytics in 2026, you have probably noticed a frustrating gap between what you ship and what your dashboard reports.
A big chunk of visitors never send events at all. It happens when the browser, an extension, or DNS level filters block requests to known tracking domains.
PostHog is a great product, but default PostHog Cloud endpoints are easy for blockers to spot. The fix is simple and practical.
The Problem: Third-Party Domains Get Blocked
Most hosted analytics setups follow the same pattern:
- Load assets from a third-party domain
- Send events to a third-party ingestion endpoint
- Hope those requests are allowed
With PostHog Cloud, those calls often go to domains like us.i.posthog.com and us-assets.i.posthog.com. Many blocklists include them, which leads to net::ERR_BLOCKED_BY_CLIENT in the console and missing data.
The Solution: First-Party Proxying
Instead of letting the browser talk to PostHog directly, proxy PostHog through your own domain. From the browser perspective, everything becomes same-origin requests like /ph/decide and /ph/e.
The mechanism in Next.js is rewrites.
Step 1: Configure Server-Side Rewrites
In next.config.ts:
async rewrites() {
return [
{
source: "/ph/array/:path*",
destination: "https://us-assets.i.posthog.com/array/:path*",
},
{
source: "/ph/:path*",
destination: "https://us.i.posthog.com/:path*",
},
];
}What this does:
- Browser requests
/ph/array/..., Next.js fetches those assets from PostHog's CDN server-side - Events and feature flag requests sent to
/ph/...get forwarded to the PostHog ingestion API - The browser no longer requests
posthog.comdomains directly
Step 2: Point the PostHog Client at Your Proxy
If you initialize PostHog with posthog-js, set api_host to your proxied path.
In src/instrumentation-client.ts:
posthog.init(process.env.NEXT_PUBLIC_POSTHOG_KEY, {
api_host: "/ph",
ui_host: process.env.NEXT_PUBLIC_POSTHOG_UI_HOST,
defaults: "2026-02-15",
});Key details:
api_host: "/ph"makes the SDK call your own domain- Your
rewrites()send those calls to PostHog Cloud - This also avoids extra DNS and TLS work for the browser
Step 3: Deploy and Test
- Open your site with an ad blocker enabled
- Open DevTools, Network tab
- You should see:
- requests to
/ph/decideand/ph/e(or similar) - requests to
/ph/array/...for config and assets - no direct requests to
us.i.posthog.comorus-assets.i.posthog.com
- requests to
If PostHog still gets blocked, it will usually show as blocked requests to your /ph/... routes. Some filters block by behavior and not only by domain.
Why This Works (And Why It's Reasonable)
Most blockers start with domain-based rules. Same-origin requests to your own domain are harder to block safely because doing so can break unrelated functionality.
This does not change what you collect. It changes where requests go from the browser's perspective.
If you care about privacy, keep your tracking scope tight:
- track only what you need
- avoid sensitive properties
- use session replay only when it is truly necessary
The Performance Bonus
Proxying is not only about blocked requests. It can also improve load behavior.
A third-party script path usually means:
- another DNS lookup
- another connection
- another TLS handshake
With a first-party path, the browser can reuse the existing connection and start downloading sooner.
Other Frameworks?
This technique works anywhere you can configure reverse proxy rules:
- Vercel:
vercel.jsonrewrites - Netlify:
_redirectsornetlify.toml - Cloudflare Workers: intercept and proxy requests
- Nginx/Apache: reverse proxy configuration
The core idea stays the same. Keep analytics traffic on your domain.
The Reality
You cannot guarantee tracking for every visitor. Some people actively choose to block analytics, and that choice should be respected.
What you can do is reduce accidental blocking, remove noisy console errors, and get a clearer picture of your traffic without switching to invasive techniques.
Try It Yourself
If you are using Next.js and PostHog Cloud:
- Add the
/phrewrites tonext.config.ts - Set
api_host: "/ph"in yourposthog.init(...) - Deploy and verify requests go to
/ph/...in the Network tab
Once it is live, your PostHog data will be more complete for visitors who are not explicitly blocking analytics.