PostHog question
How do I set up the PostHog reverse proxy in Next.js on Vercel?
Add three rewrites to next.config.js, in this order: /ingest/static/:path* to https://us-assets.i.posthog.com/static/:path*, /ingest/array/:path* to the same assets host, then /ingest/:path* to https://us.i.posthog.com/:path*. Set skipTrailingSlashRedirect: true, because PostHog requests such as /e/ end with a slash and Next.js otherwise answers them with a 308 redirect. In posthog.init use api_host: "/ingest" and ui_host: "https://us.posthog.com". PostHog says ad blockers that filter by domain do not block requests sent this way, and tells you to avoid obvious paths such as /analytics or /posthog. Use eu in the hosts for EU Cloud. Reproduced with next 15.5.28 on 2026-10-31.
Why does the proxy answer 308 on /ingest/e/?
Next.js redirects a URL that ends with a slash to the same URL without it. PostHog's guide says that without skipTrailingSlashRedirect each PostHog request gets a 308 and an extra round trip.
In a run of next dev 15.5.28 on 2026-10-31, with the rewrites pointed at a local server, a POST to /ingest/e/?ip=1 answered 308 with a Location of /ingest/e?ip=1, and nothing reached the target. With skipTrailingSlashRedirect: true the same request answered 200 and the target received the POST. The option applies to the whole app, not only /ingest, so check your own pages that end with a slash.
rewrites only: POST /ingest/e/?ip=1 -> 308, Location: /ingest/e?ip=1
skipTrailingSlashRedirect: true: POST /ingest/e/?ip=1 -> 200, target received POST /e?ip=1Why do the static and array rewrites come first?
Next.js checks rewrites in order. If the catch-all came first it would also match /ingest/static and /ingest/array, which PostHog serves from the assets host, not from the API host. A Vite app on Vercel uses the same three rules in vercel.json, in the same order, according to PostHog's Vercel proxy guide.
module.exports = {
skipTrailingSlashRedirect: true,
async rewrites() {
return [
{ source: "/ingest/static/:path*", destination: "https://us-assets.i.posthog.com/static/:path*" },
{ source: "/ingest/array/:path*", destination: "https://us-assets.i.posthog.com/array/:path*" },
{ source: "/ingest/:path*", destination: "https://us.i.posthog.com/:path*" },
]
},
}Can Next.js middleware break the proxy?
PostHog's guide warns that a middleware matcher can catch the proxy path before the rewrite runs and break ingestion. Its fix is to add the proxy path to the matcher's negative lookahead. This page did not reproduce that case.
export const config = {
matcher: ["/((?!api|ingest|_next|_vercel|.*\\..*).*)"],
}Checklist
Set it up, then check it
- 01Add the rewrites and skipTrailingSlashRedirect: true to next.config.js as shown above. /ingest is only an example: PostHog says to pick a path unique to your app, not /analytics, /tracking, /telemetry or /posthog.
- 02Point the SDK at the path and keep ui_host on PostHog's own domain, so toolbar links go to PostHog:
posthog.init(process.env.NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN, { api_host: "/ingest", ui_host: "https://us.posthog.com", }) - 03On EU Cloud use eu.i.posthog.com, eu-assets.i.posthog.com and ui_host https://eu.posthog.com.
- 04If the app has middleware, add the proxy path to its matcher so it does not run on /ingest.
- 05Redeploy. Next.js reads next.config.js at build time, and a NEXT_PUBLIC_ value is copied into the bundle when the app builds, so a variable added in Vercel later needs a new deployment.
- 06Open the deployed site with your ad blocker on and look at the Network panel: PostHog requests should go to your own domain under the proxy path with a 200 status, none to us.i.posthog.com, and none with a 308.
Related
Related guides
Sources
Official docs behind this checklist
Browse all launch guides · Read resources · How to make and check your next change
FAQ
FAQ for builders before launch
Do ad blockers still block it?
PostHog's guide says ad blockers that filter by domain will not block requests that go to your own domain, and that blockers catch obvious path names. Test with the blockers your users run.
Does proxying cost anything on Vercel?
PostHog's Vercel guide says proxied traffic is billed by Vercel as Fast Data Transfer and Edge Requests, that session recordings are the biggest driver, often 1 to 5 MB per session, and suggests its managed reverse proxy or Cloudflare Workers to avoid those costs.
Why does ui_host stay on PostHog?
PostHog says ui_host must point to its actual domain so features like the toolbar link correctly, while api_host is the relative path that sends events through your domain.