From c6bf6d389770c1e28dad61de21bb562f2b003bf9 Mon Sep 17 00:00:00 2001 From: brkalow Date: Tue, 14 Jul 2026 09:35:22 -0500 Subject: [PATCH] fix(repo): add Vercel auto-proxy opt-out --- .changeset/disable-vercel-auto-proxy.md | 7 +++++++ .../tokens/__tests__/authenticateContext.test.ts | 10 ++++++++++ packages/nextjs/src/server/clerkMiddleware.ts | 9 +++++++-- .../__tests__/mergeNextClerkPropsWithEnv.test.ts | 11 +++++++++++ packages/shared/src/__tests__/proxy.spec.ts | 13 +++++++++++++ packages/shared/src/proxy.ts | 9 +++++++++ 6 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 .changeset/disable-vercel-auto-proxy.md diff --git a/.changeset/disable-vercel-auto-proxy.md b/.changeset/disable-vercel-auto-proxy.md new file mode 100644 index 00000000000..00b38a9b49e --- /dev/null +++ b/.changeset/disable-vercel-auto-proxy.md @@ -0,0 +1,7 @@ +--- +'@clerk/backend': patch +'@clerk/nextjs': patch +'@clerk/shared': patch +--- + +Add `CLERK_DISABLE_AUTO_PROXY=true` to opt out of automatic Frontend API proxying on Vercel production deployments. diff --git a/packages/backend/src/tokens/__tests__/authenticateContext.test.ts b/packages/backend/src/tokens/__tests__/authenticateContext.test.ts index 064d5e960c7..5df5e550ba8 100644 --- a/packages/backend/src/tokens/__tests__/authenticateContext.test.ts +++ b/packages/backend/src/tokens/__tests__/authenticateContext.test.ts @@ -302,6 +302,16 @@ describe('AuthenticateContext', () => { expect(context.proxyUrl).toBeUndefined(); }); + it('does NOT auto-derive proxyUrl when auto-proxy is disabled', async () => { + process.env.CLERK_DISABLE_AUTO_PROXY = 'true'; + const clerkRequest = createClerkRequest(new Request('https://myapp-abc123.vercel.app/dashboard')); + const context = await createAuthenticateContext(clerkRequest, { + publishableKey: pkLive, + }); + + expect(context.proxyUrl).toBeUndefined(); + }); + it('explicit proxyUrl takes precedence over auto-detection', async () => { const clerkRequest = createClerkRequest(new Request('https://myapp-abc123.vercel.app/dashboard')); const context = await createAuthenticateContext(clerkRequest, { diff --git a/packages/nextjs/src/server/clerkMiddleware.ts b/packages/nextjs/src/server/clerkMiddleware.ts index 19709e6e369..9e6699de60d 100644 --- a/packages/nextjs/src/server/clerkMiddleware.ts +++ b/packages/nextjs/src/server/clerkMiddleware.ts @@ -25,7 +25,7 @@ import { clerkFrontendApiProxy, DEFAULT_PROXY_PATH, matchProxyPath } from '@cler import { isProductionFromPublishableKey, parsePublishableKey } from '@clerk/shared/keys'; import { handleNetlifyCacheInDevInstance } from '@clerk/shared/netlifyCacheHandler'; import { isMalformedURLError } from '@clerk/shared/pathMatcher'; -import { shouldAutoProxy } from '@clerk/shared/proxy'; +import { isAutoProxyDisabledFromEnvironment, shouldAutoProxy } from '@clerk/shared/proxy'; import { notFound as nextjsNotFound } from 'next/navigation'; import type { NextMiddleware, NextRequest } from 'next/server'; import { NextResponse } from 'next/server'; @@ -168,7 +168,12 @@ export const clerkMiddleware = ((...args: unknown[]): NextMiddleware | NextMiddl // Auto-detect when no explicit proxy or domain is configured const hasExplicitProxyOrDomain = resolvedParams.proxyUrl || PROXY_URL || resolvedParams.domain || DOMAIN; - if (!frontendApiProxyConfig && !hasExplicitProxyOrDomain && isProductionFromPublishableKey(publishableKey)) { + if ( + !frontendApiProxyConfig && + !hasExplicitProxyOrDomain && + !isAutoProxyDisabledFromEnvironment() && + isProductionFromPublishableKey(publishableKey) + ) { if (shouldAutoProxy(requestUrl.hostname)) { frontendApiProxyConfig = { enabled: true }; } diff --git a/packages/nextjs/src/utils/__tests__/mergeNextClerkPropsWithEnv.test.ts b/packages/nextjs/src/utils/__tests__/mergeNextClerkPropsWithEnv.test.ts index c83bdb54d0a..18bab0aa982 100644 --- a/packages/nextjs/src/utils/__tests__/mergeNextClerkPropsWithEnv.test.ts +++ b/packages/nextjs/src/utils/__tests__/mergeNextClerkPropsWithEnv.test.ts @@ -49,6 +49,17 @@ describe('mergeNextClerkPropsWithEnv', () => { expect(result.proxyUrl).toBe(''); }); + it('does not auto-derive proxyUrl when auto-proxy is disabled', () => { + process.env.CLERK_DISABLE_AUTO_PROXY = 'true'; + process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY = 'pk_live_Zm9vLmNsZXJrLmNvbSQ='; + process.env.VERCEL_TARGET_ENV = 'production'; + process.env.VERCEL_PROJECT_PRODUCTION_URL = 'myapp.vercel.app'; + + const result = mergeNextClerkPropsWithEnv({}); + + expect(result.proxyUrl).toBe(''); + }); + it('does not override an explicit proxyUrl', () => { process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY = 'pk_live_Zm9vLmNsZXJrLmNvbSQ='; process.env.VERCEL_TARGET_ENV = 'production'; diff --git a/packages/shared/src/__tests__/proxy.spec.ts b/packages/shared/src/__tests__/proxy.spec.ts index a6bd5816a39..523f99d7673 100644 --- a/packages/shared/src/__tests__/proxy.spec.ts +++ b/packages/shared/src/__tests__/proxy.spec.ts @@ -116,6 +116,19 @@ describe('getAutoProxyUrlFromEnvironment(options)', () => { ).toBe(''); }); + it('returns empty string when auto-proxy is disabled', () => { + expect( + getAutoProxyUrlFromEnvironment({ + publishableKey: 'pk_live_Zm9vLmNsZXJrLmNvbSQ=', + environment: { + CLERK_DISABLE_AUTO_PROXY: 'true', + VERCEL_PROJECT_PRODUCTION_URL: 'myapp.vercel.app', + VERCEL_TARGET_ENV: 'production', + }, + }), + ).toBe(''); + }); + it('returns empty string for ineligible or non-production Vercel environments', () => { expect( getAutoProxyUrlFromEnvironment({ diff --git a/packages/shared/src/proxy.ts b/packages/shared/src/proxy.ts index fa812d85274..67a3cd14792 100644 --- a/packages/shared/src/proxy.ts +++ b/packages/shared/src/proxy.ts @@ -1,4 +1,5 @@ import { isProductionFromPublishableKey } from './keys'; +import { isTruthy } from './underscore'; /** * @@ -55,6 +56,10 @@ function getDefaultEnvironment(): NodeJS.ProcessEnv { return typeof process !== 'undefined' && process.env ? process.env : {}; } +export function isAutoProxyDisabledFromEnvironment(environment = getDefaultEnvironment()): boolean { + return isTruthy(environment.CLERK_DISABLE_AUTO_PROXY); +} + function normalizeHostname(hostnameOrUrl: string): string { if (hostnameOrUrl.startsWith('http://') || hostnameOrUrl.startsWith('https://')) { try { @@ -90,6 +95,10 @@ export function getAutoProxyUrlFromEnvironment({ return ''; } + if (isAutoProxyDisabledFromEnvironment(environment)) { + return ''; + } + if (environment.VERCEL_TARGET_ENV !== 'production') { return ''; }