Skip to content

Commit e81c281

Browse files
edwinyjlimsarahxsanders
authored andcommitted
nuxt proxy update (#14341)
* nuxt proxy update * 3.7+ use nuxt module
1 parent b0b7af9 commit e81c281

File tree

1 file changed

+89
-33
lines changed

1 file changed

+89
-33
lines changed

contents/docs/advanced/proxy/nuxt.mdx

Lines changed: 89 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -43,30 +43,37 @@ Create a file at `server/routes/ph/[...path].ts`:
4343
```ts file=US
4444
export 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
94101
export 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
147161
Here'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

Comments
 (0)