From edfddebc03862e451ab926b8e6372d3ea0563e50 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Tue, 10 Feb 2026 19:32:37 -0800 Subject: [PATCH 1/2] fix(posthog): replace proxy rewrite with route handler for reliable body streaming --- apps/sim/app/ingest/[[...path]]/route.ts | 78 ++++++++++++++++++++++++ apps/sim/proxy.ts | 21 +------ 2 files changed, 79 insertions(+), 20 deletions(-) create mode 100644 apps/sim/app/ingest/[[...path]]/route.ts diff --git a/apps/sim/app/ingest/[[...path]]/route.ts b/apps/sim/app/ingest/[[...path]]/route.ts new file mode 100644 index 0000000000..86ba963794 --- /dev/null +++ b/apps/sim/app/ingest/[[...path]]/route.ts @@ -0,0 +1,78 @@ +import { createLogger } from '@sim/logger' +import { type NextRequest, NextResponse } from 'next/server' + +const logger = createLogger('PostHogProxy') + +const API_HOST = 'us.i.posthog.com' +const ASSET_HOST = 'us-assets.i.posthog.com' + +/** + * Builds the target PostHog URL from the incoming request path. + * Routes /ingest/static/* to the asset host, everything else to the API host. + */ +function buildTargetUrl(pathname: string, search: string): { url: string; hostname: string } { + const strippedPath = pathname.replace(/^\/ingest/, '') + const hostname = strippedPath.startsWith('/static/') ? ASSET_HOST : API_HOST + return { + url: `https://${hostname}${strippedPath}${search}`, + hostname, + } +} + +/** + * Builds forwarding headers for the PostHog request. + * Sets the Host header, forwards client IP for geolocation, + * and strips cookies/connection headers that shouldn't be forwarded. + */ +function buildHeaders(request: NextRequest, hostname: string): Headers { + const headers = new Headers(request.headers) + headers.set('host', hostname) + + const forwardedFor = request.headers.get('x-forwarded-for') + if (forwardedFor) { + headers.set('x-forwarded-for', forwardedFor) + } + + headers.delete('cookie') + headers.delete('connection') + + return headers +} + +async function handler(request: NextRequest) { + const { url, hostname } = buildTargetUrl(request.nextUrl.pathname, request.nextUrl.search) + const headers = buildHeaders(request, hostname) + + const hasBody = !['GET', 'HEAD'].includes(request.method) + + try { + const response = await fetch(url, { + method: request.method, + headers, + ...(hasBody ? { body: request.body, duplex: 'half' } : {}), + } as RequestInit) + + const responseHeaders = new Headers(response.headers) + responseHeaders.delete('content-encoding') + responseHeaders.delete('transfer-encoding') + + return new NextResponse(response.body, { + status: response.status, + headers: responseHeaders, + }) + } catch (error) { + logger.error('PostHog proxy error', { + url, + method: request.method, + error: error instanceof Error ? error.message : String(error), + }) + return new NextResponse(null, { status: 502 }) + } +} + +export const GET = handler +export const POST = handler +export const PUT = handler +export const PATCH = handler +export const DELETE = handler +export const OPTIONS = handler diff --git a/apps/sim/proxy.ts b/apps/sim/proxy.ts index c90df2eec7..36ada3484f 100644 --- a/apps/sim/proxy.ts +++ b/apps/sim/proxy.ts @@ -140,24 +140,6 @@ function handleSecurityFiltering(request: NextRequest): NextResponse | null { export async function proxy(request: NextRequest) { const url = request.nextUrl - if (url.pathname.startsWith('/ingest/')) { - const hostname = url.pathname.startsWith('/ingest/static/') - ? 'us-assets.i.posthog.com' - : 'us.i.posthog.com' - - const targetPath = url.pathname.replace(/^\/ingest/, '') - const targetUrl = `https://${hostname}${targetPath}${url.search}` - - return NextResponse.rewrite(new URL(targetUrl), { - request: { - headers: new Headers({ - ...Object.fromEntries(request.headers), - host: hostname, - }), - }, - }) - } - const sessionCookie = getSessionCookie(request) const hasActiveSession = isAuthDisabled || !!sessionCookie @@ -219,7 +201,6 @@ export async function proxy(request: NextRequest) { export const config = { matcher: [ - '/ingest/:path*', // PostHog proxy for session recording '/', // Root path for self-hosted redirect logic '/terms', // Whitelabel terms redirect '/privacy', // Whitelabel privacy redirect @@ -230,6 +211,6 @@ export const config = { '/signup', '/invite/:path*', // Match invitation routes // Catch-all for other pages, excluding static assets and public directories - '/((?!_next/static|_next/image|favicon.ico|logo/|static/|footer/|social/|enterprise/|favicon/|twitter/|robots.txt|sitemap.xml).*)', + '/((?!_next/static|_next/image|ingest|favicon.ico|logo/|static/|footer/|social/|enterprise/|favicon/|twitter/|robots.txt|sitemap.xml).*)', ], } From 5f5531d22cde7ee817fd403721b2427feae9d5fa Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Tue, 10 Feb 2026 19:49:03 -0800 Subject: [PATCH 2/2] fix posthog --- apps/sim/app/ingest/[[...path]]/route.ts | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/apps/sim/app/ingest/[[...path]]/route.ts b/apps/sim/app/ingest/[[...path]]/route.ts index 86ba963794..39c537bc93 100644 --- a/apps/sim/app/ingest/[[...path]]/route.ts +++ b/apps/sim/app/ingest/[[...path]]/route.ts @@ -21,18 +21,12 @@ function buildTargetUrl(pathname: string, search: string): { url: string; hostna /** * Builds forwarding headers for the PostHog request. - * Sets the Host header, forwards client IP for geolocation, - * and strips cookies/connection headers that shouldn't be forwarded. + * Sets the Host header and strips cookies/connection headers + * that shouldn't be forwarded. */ function buildHeaders(request: NextRequest, hostname: string): Headers { const headers = new Headers(request.headers) headers.set('host', hostname) - - const forwardedFor = request.headers.get('x-forwarded-for') - if (forwardedFor) { - headers.set('x-forwarded-for', forwardedFor) - } - headers.delete('cookie') headers.delete('connection') @@ -54,6 +48,7 @@ async function handler(request: NextRequest) { const responseHeaders = new Headers(response.headers) responseHeaders.delete('content-encoding') + responseHeaders.delete('content-length') responseHeaders.delete('transfer-encoding') return new NextResponse(response.body, {