SvelteKit reverse proxy
Contents
- If you use a self-hosted proxy, PostHog can't help troubleshoot. Use our managed reverse proxy if you want support.
- Use domains matching your PostHog region:
us.i.posthog.comfor US,eu.i.posthog.comfor EU. - Don't use obvious path names like
/analytics,/tracking,/telemetry, or/posthog. Blockers will catch them. Use something unique to your app instead.
This guide shows you how to use SvelteKit server hooks as a reverse proxy for PostHog.
How it works
SvelteKit's handle hook runs on every request before it reaches your routes. When a request matches your proxy path, the hook fetches the response from PostHog and returns it under your domain.
Here's the request flow:
- User triggers an event in your app
- Request goes to your domain (e.g.,
yourdomain.com/ph/e) - SvelteKit's server hook intercepts the request
- The hook fetches the response from PostHog's servers
- PostHog's response is returned to the user under your domain
This works because the hook runs server-side, so the browser only sees requests to your domain. Ad blockers that filter by domain won't block these requests.
This method only works with server-side rendering enabled. If you're using adapter-static for static site generation, SvelteKit ignores hooks.server.ts. Use a CDN-level proxy like Cloudflare Workers or Netlify redirects instead.
Prerequisites
This guide requires a SvelteKit project with SSR enabled (the default).
Setup
- 1
Create the server hook
Create a file at
src/hooks.server.ts:Here's what the code does:
- Intercepts requests starting with
/ph - Routes
/static/*requests to PostHog's asset server and everything else to the main API - Sets the
hostheader so PostHog can route the request correctly - Forwards the client's IP address for accurate geolocation
- Passes non-matching requests to the normal SvelteKit request handler
See SvelteKit's hooks documentation for more details.
- Intercepts requests starting with
- 2
Update your PostHog SDK
In your SvelteKit app, initialize PostHog with your proxy path. Create or update your PostHog setup:
Then call it from your root layout:
SvelteThe
api_hosttells the SDK where to send events. Using a relative path ensures requests go to your domain. Theui_hostmust point to PostHog's actual domain so features like the toolbar link correctly. - 3
Deploy your changes
Commit and push your changes. The server hook will be active once deployed.
In development, restart your dev server after creating the hook file.
Verify your setup
CheckpointConfirm events are flowing through your proxy:
- Open your browser's developer tools and go to the Network tab
- Navigate to your site or trigger an event
- Look for requests to your domain with your proxy path (e.g.,
yourdomain.com/ph) - Verify the response status is
200 OK - Check the PostHog app to confirm events appear in your activity feed
If you see errors, check troubleshooting below.
Troubleshooting
Hook not running
If requests to your proxy path return 404 or aren't being proxied:
- Verify your file is at
src/hooks.server.ts(notsrc/routes/hooks.server.ts) - Check the file extension is
.tsfor TypeScript projects - Restart your dev server after creating the file
Static site generation
SvelteKit server hooks don't run for statically generated sites. If you're using adapter-static, use a different proxy method:
- Cloudflare Workers for sites on Cloudflare Pages
- Netlify redirects for sites on Netlify
- Vercel rewrites for sites on Vercel
All users show same location
If geolocation data is wrong or all users appear in the same location:
- Verify the
x-forwarded-forheader is being set in your hook - If you're behind multiple proxies (like Cloudflare or a load balancer), the original client IP may be in a different header. Try using the incoming
x-forwarded-forheader first.
The code above handles this by checking both x-forwarded-for from the incoming request and event.getClientAddress() as a fallback.
Deployment notes
This proxy works with these SvelteKit adapters:
- adapter-node: Works out of the box
- adapter-vercel: Works with SSR enabled
- adapter-netlify: Works with SSR enabled
- adapter-cloudflare: Works with SSR enabled
For adapter-static, use a CDN-level proxy instead.