@@ -54,6 +54,13 @@ const HOP_BY_HOP_HEADERS = [
5454 'upgrade' ,
5555] ;
5656
57+ // Headers to strip from proxied responses. fetch() auto-decompresses
58+ // response bodies, so Content-Encoding no longer describes the body
59+ // and Content-Length reflects the compressed size. We request identity
60+ // encoding upstream to avoid the double compression pass, but strip
61+ // these defensively since servers may ignore Accept-Encoding: identity.
62+ const RESPONSE_HEADERS_TO_STRIP = [ 'content-encoding' , 'content-length' ] ;
63+
5764/**
5865 * Derives the Frontend API URL from a publishable key.
5966 * @param publishableKey - The Clerk publishable key
@@ -235,6 +242,11 @@ export async function clerkFrontendApiProxy(request: Request, options?: Frontend
235242 const fapiHost = new URL ( fapiBaseUrl ) . host ;
236243 headers . set ( 'Host' , fapiHost ) ;
237244
245+ // Request uncompressed responses to avoid a double compression pass.
246+ // fetch() auto-decompresses, so without this FAPI compresses → fetch
247+ // decompresses → the serving layer re-compresses for the browser.
248+ headers . set ( 'Accept-Encoding' , 'identity' ) ;
249+
238250 // Set X-Forwarded-* headers for proxy awareness
239251 // Only set these if not already present (preserve values from upstream proxies)
240252 if ( ! headers . has ( 'X-Forwarded-Host' ) ) {
@@ -271,10 +283,11 @@ export async function clerkFrontendApiProxy(request: Request, options?: Frontend
271283
272284 const response = await fetch ( targetUrl . toString ( ) , fetchOptions ) ;
273285
274- // Build response headers, excluding hop-by-hop headers
286+ // Build response headers, excluding hop-by-hop and encoding headers
275287 const responseHeaders = new Headers ( ) ;
276288 response . headers . forEach ( ( value , key ) => {
277- if ( ! HOP_BY_HOP_HEADERS . includes ( key . toLowerCase ( ) ) ) {
289+ const lower = key . toLowerCase ( ) ;
290+ if ( ! HOP_BY_HOP_HEADERS . includes ( lower ) && ! RESPONSE_HEADERS_TO_STRIP . includes ( lower ) ) {
278291 responseHeaders . set ( key , value ) ;
279292 }
280293 } ) ;
@@ -295,11 +308,20 @@ export async function clerkFrontendApiProxy(request: Request, options?: Frontend
295308 }
296309 }
297310
298- return new Response ( response . body , {
311+ const proxyResponse = new Response ( response . body , {
299312 status : response . status ,
300313 statusText : response . statusText ,
301314 headers : responseHeaders ,
302315 } ) ;
316+
317+ // Some runtimes may re-add Content-Length when constructing the Response.
318+ // Delete explicitly since fetch() decoded the body and the original values
319+ // no longer reflect the actual content.
320+ for ( const header of RESPONSE_HEADERS_TO_STRIP ) {
321+ proxyResponse . headers . delete ( header ) ;
322+ }
323+
324+ return proxyResponse ;
303325 } catch ( error ) {
304326 const message = error instanceof Error ? error . message : 'Unknown error' ;
305327 return createErrorResponse ( 'proxy_request_failed' , `Failed to proxy request to Clerk FAPI: ${ message } ` , 502 ) ;
0 commit comments