@@ -43,30 +43,37 @@ Create a file at `server/routes/ph/[...path].ts`:
4343``` ts file=US
4444export default defineEventHandler (async (event ) => {
4545 const path = event .context .params ?.path || ' '
46+ const url = getRequestURL (event )
47+ const search = url .search || ' '
4648
4749 const hostname = path .startsWith (' static/' )
4850 ? ' us-assets.i.posthog.com'
4951 : ' us.i.posthog.com'
5052
51- const targetUrl = ` https://${hostname }/${path } `
53+ const targetUrl = ` https://${hostname }/${path }${ search } `
5254
55+ // Forward headers, excluding ones that shouldn't be proxied
5356 const headers = new Headers ()
54- headers . set ( ' host' , hostname )
57+ const excludedHeaders = [ ' host' , ' connection ' , ' content-length ' , ' transfer-encoding ' , ' accept-encoding ' ]
5558
56- // Forward relevant headers
57- const contentType = getHeader (event , ' content-type' )
58- if (contentType ) {
59- headers .set (' content-type' , contentType )
59+ const requestHeaders = getRequestHeaders (event )
60+ for (const [key, value] of Object .entries (requestHeaders )) {
61+ if (value && ! excludedHeaders .includes (key .toLowerCase ())) {
62+ headers .set (key , value )
63+ }
6064 }
6165
66+ headers .set (' host' , hostname )
67+
6268 // Forward client IP for geolocation
6369 const clientIp = getHeader (event , ' x-forwarded-for' ) || getRequestIP (event )
6470 if (clientIp ) {
6571 headers .set (' x-forwarded-for' , clientIp )
6672 }
6773
74+ // Read body as binary buffer to preserve gzip compression
6875 const body = event .method !== ' GET' && event .method !== ' HEAD'
69- ? await readRawBody (event )
76+ ? await readRawBody (event , false )
7077 : undefined
7178
7279 const response = await fetch (targetUrl , {
@@ -75,48 +82,55 @@ export default defineEventHandler(async (event) => {
7582 body ,
7683 })
7784
78- // Copy response headers
79- const responseHeaders: Record <string , string > = {}
80- response .headers .forEach ((value , key ) => {
81- if (key .toLowerCase () !== ' content-encoding' && key .toLowerCase () !== ' content-length' ) {
82- responseHeaders [key ] = value
85+ // Copy response headers, excluding problematic ones
86+ for (const [key, value] of response .headers .entries ()) {
87+ if (! [' content-encoding' , ' content-length' , ' transfer-encoding' ].includes (key .toLowerCase ())) {
88+ setResponseHeader (event , key , value )
8389 }
84- })
90+ }
8591
86- setResponseHeaders (event , responseHeaders )
8792 setResponseStatus (event , response .status )
8893
89- return response .text ()
94+ // Return binary response
95+ const arrayBuffer = await response .arrayBuffer ()
96+ return Buffer .from (arrayBuffer )
9097})
9198```
9299
93100``` ts file=EU
94101export default defineEventHandler (async (event ) => {
95102 const path = event .context .params ?.path || ' '
103+ const url = getRequestURL (event )
104+ const search = url .search || ' '
96105
97106 const hostname = path .startsWith (' static/' )
98107 ? ' eu-assets.i.posthog.com'
99108 : ' eu.i.posthog.com'
100109
101- const targetUrl = ` https://${hostname }/${path } `
110+ const targetUrl = ` https://${hostname }/${path }${ search } `
102111
112+ // Forward headers, excluding ones that shouldn't be proxied
103113 const headers = new Headers ()
104- headers . set ( ' host' , hostname )
114+ const excludedHeaders = [ ' host' , ' connection ' , ' content-length ' , ' transfer-encoding ' , ' accept-encoding ' ]
105115
106- // Forward relevant headers
107- const contentType = getHeader (event , ' content-type' )
108- if (contentType ) {
109- headers .set (' content-type' , contentType )
116+ const requestHeaders = getRequestHeaders (event )
117+ for (const [key, value] of Object .entries (requestHeaders )) {
118+ if (value && ! excludedHeaders .includes (key .toLowerCase ())) {
119+ headers .set (key , value )
120+ }
110121 }
111122
123+ headers .set (' host' , hostname )
124+
112125 // Forward client IP for geolocation
113126 const clientIp = getHeader (event , ' x-forwarded-for' ) || getRequestIP (event )
114127 if (clientIp ) {
115128 headers .set (' x-forwarded-for' , clientIp )
116129 }
117130
131+ // Read body as binary buffer to preserve gzip compression
118132 const body = event .method !== ' GET' && event .method !== ' HEAD'
119- ? await readRawBody (event )
133+ ? await readRawBody (event , false )
120134 : undefined
121135
122136 const response = await fetch (targetUrl , {
@@ -125,18 +139,18 @@ export default defineEventHandler(async (event) => {
125139 body ,
126140 })
127141
128- // Copy response headers
129- const responseHeaders: Record <string , string > = {}
130- response .headers .forEach ((value , key ) => {
131- if (key .toLowerCase () !== ' content-encoding' && key .toLowerCase () !== ' content-length' ) {
132- responseHeaders [key ] = value
142+ // Copy response headers, excluding problematic ones
143+ for (const [key, value] of response .headers .entries ()) {
144+ if (! [' content-encoding' , ' content-length' , ' transfer-encoding' ].includes (key .toLowerCase ())) {
145+ setResponseHeader (event , key , value )
133146 }
134- })
147+ }
135148
136- setResponseHeaders (event , responseHeaders )
137149 setResponseStatus (event , response .status )
138150
139- return response .text ()
151+ // Return binary response
152+ const arrayBuffer = await response .arrayBuffer ()
153+ return Buffer .from (arrayBuffer )
140154})
141155```
142156
@@ -147,15 +161,57 @@ The `[...path]` in the filename is a Nuxt [catch-all route](https://nuxt.com/doc
147161Here's what the code does:
148162
149163- Routes ` /static/* ` requests to PostHog's asset server and everything else to the main API
150- - Sets the ` host ` header so PostHog can route the request correctly
164+ - Preserves query parameters (like ` ?compression=gzip-js ` ) required for compressed requests
165+ - Forwards relevant headers while excluding ones that shouldn't be proxied
166+ - Reads the request body as binary to preserve gzip-compressed data
151167- Forwards the client's IP address for accurate geolocation
152- - Strips content encoding headers to avoid issues with compressed responses
168+ - Returns the response as binary data to handle all content types correctly
153169
154170</Step >
155171
156172<Step title = " Update your PostHog SDK" >
157173
158- In your Nuxt app, update your PostHog initialization to use your proxy path:
174+ Update your PostHog configuration to use your proxy path.
175+
176+ ### Using @posthog/nuxt (Nuxt 3.7+)
177+
178+ If you're using the ` @posthog/nuxt ` module, update your ` nuxt.config.ts ` :
179+
180+ <MultiLanguage >
181+
182+ ``` ts file=US
183+ // nuxt.config.ts
184+ export default defineNuxtConfig ({
185+ modules: [' @posthog/nuxt' ],
186+ posthogConfig: {
187+ publicKey: ' <ph_project_api_key>' ,
188+ clientConfig: {
189+ api_host: ' /ph' ,
190+ ui_host: ' https://us.posthog.com'
191+ }
192+ }
193+ })
194+ ```
195+
196+ ``` ts file=EU
197+ // nuxt.config.ts
198+ export default defineNuxtConfig ({
199+ modules: [' @posthog/nuxt' ],
200+ posthogConfig: {
201+ publicKey: ' <ph_project_api_key>' ,
202+ clientConfig: {
203+ api_host: ' /ph' ,
204+ ui_host: ' https://eu.posthog.com'
205+ }
206+ }
207+ })
208+ ```
209+
210+ </MultiLanguage >
211+
212+ ### Using posthog-js plugin (Nuxt 3.0-3.6)
213+
214+ If you're using the ` posthog-js ` library directly with a plugin:
159215
160216<MultiLanguage >
161217
0 commit comments