Last active
October 30, 2025 21:55
-
-
Save arpitdalal/ccc807fa6a15638b86a128d9b7ac51a1 to your computer and use it in GitHub Desktop.
A reverse proxy for PostHog using react-router
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import { type LoaderFunctionArgs, type ActionFunctionArgs } from "react-router"; | |
| const API_HOST = "us.i.posthog.com"; // use eu.i.posthog.com for EU | |
| const ASSET_HOST = "us-assets.i.posthog.com"; // use eu-assets.i.posthog.com for EU | |
| type RequestInitWithDuplex = RequestInit & { | |
| duplex?: "half"; // https://github.com/microsoft/TypeScript-DOM-lib-generator/issues/1483 | |
| }; | |
| async function posthogProxy(request: Request) { | |
| const url = new URL(request.url); | |
| // Choose the right host based on the request path | |
| const hostname = url.pathname.startsWith("/resources/ingest/static") | |
| ? ASSET_HOST | |
| : API_HOST; | |
| // Build the new URL to forward to PostHog | |
| const newUrl = new URL(url); | |
| newUrl.protocol = "https"; | |
| newUrl.hostname = hostname; | |
| newUrl.port = "443"; | |
| newUrl.pathname = newUrl.pathname.replace(/^\/resources\/ingest/, ""); | |
| // ^ depends on the endpoint | |
| // Prepare headers | |
| const headers = new Headers(request.headers); | |
| headers.set("host", hostname); | |
| headers.delete("accept-encoding"); // to let the fetch handle compression | |
| // Setup fetch options | |
| const fetchOptions: RequestInitWithDuplex = { | |
| method: request.method, | |
| headers, | |
| redirect: "follow", // enable fetch to follow the redirect in case PostHog throws a redirect | |
| }; | |
| // Add body for non-GET/HEAD requests | |
| if (!["GET", "HEAD"].includes(request.method)) { | |
| fetchOptions.body = request.body; | |
| fetchOptions.duplex = "half"; // needed for larger POST bodies | |
| } | |
| try { | |
| // Forward the request to PostHog | |
| const response = await fetch(newUrl, fetchOptions); | |
| // Clean up response headers | |
| const responseHeaders = new Headers(response.headers); | |
| responseHeaders.delete("content-encoding"); | |
| responseHeaders.delete("content-length"); | |
| responseHeaders.delete("transfer-encoding"); | |
| responseHeaders.delete("Content-Length"); | |
| // Get response data if needed | |
| const data = | |
| response.status === 304 || response.status === 204 | |
| ? null | |
| : await response.arrayBuffer(); | |
| // Return the response | |
| return new Response(data, { | |
| status: response.status, | |
| statusText: response.statusText, | |
| headers: responseHeaders, | |
| }); | |
| } catch (error) { | |
| // Track and log the error | |
| console.error("Proxy error:", error); | |
| return new Response("Proxy Error", { status: 500 }); | |
| } | |
| } | |
| export async function loader({ request }: LoaderFunctionArgs) { | |
| return await posthogProxy(request); | |
| } | |
| export async function action({ request }: ActionFunctionArgs) { | |
| return await posthogProxy(request); | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| export posthog.init('<YOUR_API_KEY>', { | |
| api_host: '/resources/ingest' // point to the proxy endpoint instead of PostHog's servers | |
| ui_host: 'https://us.posthog.com', // use https://eu.posthog.com for EU | |
| // ^ Necessary when using reverse proxy - https://posthog.com/docs/libraries/js#config | |
| }) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment