feat(*): auto-proxy for eligible hosts - #8035

Merged
nikosdouvlis merged 35 commits into
mainfrom
brkalow/auto-proxy-vercel-subdomains
Apr 28, 2026
Merged

feat(*): auto-proxy for eligible hosts#8035
nikosdouvlis merged 35 commits into
mainfrom
brkalow/auto-proxy-vercel-subdomains

Conversation

@brkalow

@brkalowbrkalow commented Mar 11, 2026

Copy link
Copy Markdown
Member

Description

This PR implements automatic FAPI proxying for Vercel preview deployments. When an app is deployed to a .vercel.app subdomain without explicit proxy or domain configuration, the SDK automatically routes FAPI requests through the app's own domain via the /__clerk proxy path.

The auto-detection is centralized across all SDKs:

  • Shared utilities (@clerk/shared/proxy): New isVercelPreviewDeploy() helper detects .vercel.app hostnames
  • Client-side (@clerk/clerk-js): Auto-detect in proxyUrl getter when window.location.hostname is .vercel.app
  • Server-side (@clerk/backend): Auto-detect in authenticateContext constructor when request hostname is .vercel.app
  • Next.js middleware (@clerk/nextjs): Auto-enable proxy interception for /__clerk/* requests on .vercel.app hostnames

Guard conditions prevent auto-detection when explicit configuration is provided: proxyUrl, domain, or environment variables NEXT_PUBLIC_CLERK_PROXY_URL / NEXT_PUBLIC_CLERK_DOMAIN.

Test Results

  • @clerk/shared: 981/981 tests passed
  • @clerk/backend: 1114/1114 tests passed
  • @clerk/clerk-js: 624/624 tests passed
  • @clerk/nextjs: 362 tests passed (49 pre-existing failures unrelated to this change)

Build verification: All packages build cleanly with no type errors.

Checklist

  • pnpm test runs as expected
  • pnpm build runs as expected
  • Tests added for new functionality

Type of change

  • 🌟 New feature

Summary by CodeRabbit

  • New Features

    • Automatic proxy detection for Vercel-style hosts: when no explicit proxy or domain is provided, the frontend API routing can auto-configure a relative proxy for eligible deployments; explicit proxy or domain settings take precedence.
  • Bug Fixes

    • Improved handling of relative proxy URLs in non-browser/server contexts and script-host resolution fallback.
  • Tests

    • Added comprehensive tests validating auto-detection, precedence rules, environment-based behavior, and various host scenarios.

@vercel

vercelBot commented Mar 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentApr 27, 2026 7:48pm

Request Review

@changeset-bot

changeset-botBot commented Mar 11, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: d0450ed

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 20 packages
NameType
@clerk/sharedPatch
@clerk/backendPatch
@clerk/clerk-jsPatch
@clerk/nextjsPatch
@clerk/astroPatch
@clerk/chrome-extensionPatch
@clerk/expo-passkeysPatch
@clerk/expoPatch
@clerk/expressPatch
@clerk/fastifyPatch
@clerk/honoPatch
@clerk/localizationsPatch
@clerk/mswPatch
@clerk/nuxtPatch
@clerk/react-routerPatch
@clerk/reactPatch
@clerk/tanstack-react-startPatch
@clerk/testingPatch
@clerk/uiPatch
@clerk/vuePatch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitaiBot commented Mar 11, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds exported functions shouldAutoProxy(hostname: string) and getAutoProxyUrlFromEnvironment(...) to the shared package to detect eligible hostnames (e.g., *.vercel.app) and derive a relative proxy path (/__clerk). Integrates this auto-proxy logic into backend AuthenticateContext, clerk-js core, Next.js middleware, and environment-merging utilities so that when neither proxyUrl nor domain is provided, the proxy may be set to the current origin + /__clerk for eligible environments. Adds tests across packages and a changeset entry.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 20.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title 'feat(*): auto-proxy for eligible hosts' accurately describes the main feature being implemented across the codebase - automatic proxy detection for eligible hostnames (specifically .vercel.app subdomains).

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

…omains
Add automatic FAPI proxying detection for Vercel preview deployments across all SDKs. When an app is deployed to a .vercel.app subdomain without explicit proxy/domain configuration, the SDK automatically routes FAPI requests through the app's own domain via /__clerk proxy path.
- Add isVercelPreviewDeploy() helper in @clerk/shared/proxy
- Auto-detect in clerk-js proxyUrl getter for client-side SDK initialization
- Auto-detect in @clerk/backend authenticateContext for server-side auth
- Enable proxy interception in Next.js middleware for /__clerk/* requests on .vercel.app
- Add comprehensive tests for all three layers (shared, backend, clerk-js, nextjs)
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
@pkg-pr-new

pkg-pr-newBot commented Mar 13, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

npm i https://pkg.pr.new/@clerk/astro@8035

@clerk/backend

npm i https://pkg.pr.new/@clerk/backend@8035

@clerk/chrome-extension

npm i https://pkg.pr.new/@clerk/chrome-extension@8035

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@8035

@clerk/dev-cli

npm i https://pkg.pr.new/@clerk/dev-cli@8035

@clerk/expo

npm i https://pkg.pr.new/@clerk/expo@8035

@clerk/expo-passkeys

npm i https://pkg.pr.new/@clerk/expo-passkeys@8035

@clerk/express

npm i https://pkg.pr.new/@clerk/express@8035

@clerk/fastify

npm i https://pkg.pr.new/@clerk/fastify@8035

@clerk/hono

npm i https://pkg.pr.new/@clerk/hono@8035

@clerk/localizations

npm i https://pkg.pr.new/@clerk/localizations@8035

@clerk/nextjs

npm i https://pkg.pr.new/@clerk/nextjs@8035

@clerk/nuxt

npm i https://pkg.pr.new/@clerk/nuxt@8035

@clerk/react

npm i https://pkg.pr.new/@clerk/react@8035

@clerk/react-router

npm i https://pkg.pr.new/@clerk/react-router@8035

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@8035

@clerk/tanstack-react-start

npm i https://pkg.pr.new/@clerk/tanstack-react-start@8035

@clerk/testing

npm i https://pkg.pr.new/@clerk/testing@8035

@clerk/ui

npm i https://pkg.pr.new/@clerk/ui@8035

@clerk/upgrade

npm i https://pkg.pr.new/@clerk/upgrade@8035

@clerk/vue

npm i https://pkg.pr.new/@clerk/vue@8035

commit: d0450ed

@brkalowbrkalow changed the title Auto-proxy for .vercel.app subdomainsfeat(*): auto-proxy for eligible hostsMar 13, 2026

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.changeset/tiny-badgers-smile.md:
- Line 8: Update the changeset description sentence that currently reads "Add
auto-proxy detection for eligible hosts and generalize the internal helper
naming for future providers." to be specific about which hosts are considered
eligible (e.g., Vercel preview deployments) and to clarify the helper change;
for example, change the description to: "Add auto-proxy detection for Vercel
preview deployments (.vercel.app subdomains) and generalize the internal helper
for future hosting providers." Locate and edit the line containing that
description in the changeset (the sentence beginning "Add auto-proxy detection
for eligible hosts...") and replace it with the clearer, user-facing wording.
- Around line 1-6: The changeset incorrectly marks package bumps as 'patch' but
a new public API (shouldAutoProxy) was added; update the entries so any package
that exports the new function (at least '@clerk/shared', and any packages that
re-export it like '@clerk/backend', '@clerk/clerk-js', '@clerk/nextjs' if
applicable) use 'minor' instead of 'patch' in the
.changeset/tiny-badgers-smile.md file; ensure the header lines for those package
entries read 'minor' to reflect the new public API bump while keeping other
metadata unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0cecde51-9659-43c2-8677-37774a033a71

📥 Commits

Reviewing files that changed from the base of the PR and between dbc580e and 2d729d2.

📒 Files selected for processing (1)
  • .changeset/tiny-badgers-smile.md

Comment thread.changeset/tiny-badgers-smile.md Outdated
Comment on lines +1 to +6
---
'@clerk/backend': patch
'@clerk/clerk-js': patch
'@clerk/nextjs': patch
'@clerk/shared': patch
---

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash# Description: Check if shouldAutoProxy is a public export# Search for shouldAutoProxy exports in the shared package
rg -n "export.*shouldAutoProxy" packages/shared/

Repository: clerk/javascript

Length of output: 152


Update version bumps from patch to minor to reflect the new public API export.

The changeset marks all packages for patch bumps, but the PR introduces a new public API function shouldAutoProxy exported from @clerk/shared. Per semantic versioning standards, new public APIs require minor version bumps, not patch (which is reserved for bug fixes). Update the changeset accordingly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.changeset/tiny-badgers-smile.md around lines 1 - 6, The changeset
incorrectly marks package bumps as 'patch' but a new public API
(shouldAutoProxy) was added; update the entries so any package that exports the
new function (at least '@clerk/shared', and any packages that re-export it like
'@clerk/backend', '@clerk/clerk-js', '@clerk/nextjs' if applicable) use 'minor'
instead of 'patch' in the .changeset/tiny-badgers-smile.md file; ensure the
header lines for those package entries read 'minor' to reflect the new public
API bump while keeping other metadata unchanged.

Comment thread.changeset/tiny-badgers-smile.md Outdated
@brkalow

Copy link
Copy Markdown
MemberAuthor

!snapshot

@github-actions

This comment has been minimized.

@brkalow

Copy link
Copy Markdown
MemberAuthor

!snapshot

@github-actions

This comment has been minimized.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/shared/src/proxy.ts`:
- Around line 45-47: The normalizeHostname function can throw when called with a
malformed URL (e.g., VERCEL_PROJECT_PRODUCTION_URL); wrap the URL parsing in a
try-catch inside normalizeHostname (the function shown using new
URL(...).hostname) and return an empty string on error so config resolution
doesn't crash; ensure callers that rely on normalizeHostname (where
VERCEL_PROJECT_PRODUCTION_URL is passed) can handle an empty string result
gracefully.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: 2ad9a329-ea13-4ffe-86f9-f7dec3b163e7

📥 Commits

Reviewing files that changed from the base of the PR and between 2d729d2 and 72d6bdc.

📒 Files selected for processing (5)
  • .changeset/tiny-badgers-smile.md
  • packages/nextjs/src/utils/__tests__/mergeNextClerkPropsWithEnv.test.ts
  • packages/nextjs/src/utils/mergeNextClerkPropsWithEnv.ts
  • packages/shared/src/__tests__/proxy.spec.ts
  • packages/shared/src/proxy.ts

Comment threadpackages/shared/src/proxy.ts Outdated
Comment on lines +45 to +47
function normalizeHostname(hostnameOrUrl: string): string {
if (hostnameOrUrl.startsWith('http://') || hostnameOrUrl.startsWith('https://')) {
return new URL(hostnameOrUrl).hostname;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash# Verify call path and URL constructor throw behavior
rg -n "normalizeHostname|getAutoProxyUrlFromEnvironment|VERCEL_PROJECT_PRODUCTION_URL" \
packages/shared/src/proxy.ts packages/nextjs/src/utils/mergeNextClerkPropsWithEnv.ts
node - <<'NODE'const samples = ['https://', 'https://?x=1', 'https:///foo', 'https://myapp.vercel.app'];for (const s of samples) { try { console.log(`${s} => hostname="${new URL(s).hostname}"`); } catch (e) { console.log(`${s} => THROWS (${e.name})`); }}NODE

Repository: clerk/javascript

Length of output: 853


🏁 Script executed:

#!/bin/bash# Get full function context at line 45-47 and surrounding linesecho"=== normalizeHostname function context (lines 41-55) ==="
sed -n '41,55p' packages/shared/src/proxy.ts | cat -n
echo -e "\n=== getAutoProxyUrlFromEnvironment call context (lines 70-80) ==="
sed -n '70,80p' packages/shared/src/proxy.ts | cat -n
# Search for tests related to proxy.ts or normalizeHostnameecho -e "\n=== Tests for proxy functions ==="
find . -name "*.test.ts" -o -name "*.spec.ts"| xargs grep -l "normalizeHostname\|getAutoProxyUrlFromEnvironment\|proxy"2>/dev/null | head -10

Repository: clerk/javascript

Length of output: 1707


🏁 Script executed:

#!/bin/bash# Check if there are tests covering error cases for these functionsecho"=== Searching for test files related to proxy ==="
fd -e "test.ts" -e "spec.ts"| xargs grep -l "normalizeHostname\|getAutoProxyUrlFromEnvironment"2>/dev/null
# Also check the test directory structureecho -e "\n=== Checking __tests__ and test directory structure ==="
find packages/shared -type d \( -name "__tests__" -o -name "tests" -o -name "test"\)| head -5

Repository: clerk/javascript

Length of output: 410


🏁 Script executed:

#!/bin/bash# Read the existing proxy test file to see current coverageecho"=== Existing proxy tests ==="
cat packages/shared/src/__tests__/proxy.spec.ts

Repository: clerk/javascript

Length of output: 5211


Handle malformed VERCEL_PROJECT_PRODUCTION_URL in hostname normalization to prevent runtime crashes.

At line 76, normalizeHostname() is called with an environment variable without error handling. If VERCEL_PROJECT_PRODUCTION_URL is malformed (e.g., https:// or https://?x=1), new URL() throws a TypeError, crashing config resolution instead of gracefully returning an empty string. Add try-catch to safely handle this:

Proposed fix
function normalizeHostname(hostnameOrUrl: string): string {
if (hostnameOrUrl.startsWith('http://') || hostnameOrUrl.startsWith('https://')) {
- return new URL(hostnameOrUrl).hostname;+ try {+ return new URL(hostnameOrUrl).hostname;+ } catch {+ return '';+ }
}
return hostnameOrUrl.split('/')[0] || '';
}

Also applies to: 76-77

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/shared/src/proxy.ts` around lines 45 - 47, The normalizeHostname
function can throw when called with a malformed URL (e.g.,
VERCEL_PROJECT_PRODUCTION_URL); wrap the URL parsing in a try-catch inside
normalizeHostname (the function shown using new URL(...).hostname) and return an
empty string on error so config resolution doesn't crash; ensure callers that
rely on normalizeHostname (where VERCEL_PROJECT_PRODUCTION_URL is passed) can
handle an empty string result gracefully.

@brkalow

Copy link
Copy Markdown
MemberAuthor

!snapshot

@github-actions

This comment has been minimized.

@Railly

Copy link
Copy Markdown
Contributor

!snapshot

@github-actions

Copy link
Copy Markdown
Contributor

Hey @Railly - the snapshot version command generated the following package versions:

PackageVersion
@clerk/astro3.0.18-snapshot.v20260421194054
@clerk/backend3.2.14-snapshot.v20260421194054
@clerk/chrome-extension3.1.15-snapshot.v20260421194054
@clerk/clerk-js6.7.5-snapshot.v20260421194054
@clerk/dev-cli0.1.1-snapshot.v20260421194054
@clerk/expo3.2.2-snapshot.v20260421194054
@clerk/expo-passkeys1.0.15-snapshot.v20260421194054
@clerk/express2.1.6-snapshot.v20260421194054
@clerk/fastify3.1.16-snapshot.v20260421194054
@clerk/hono0.1.16-snapshot.v20260421194054
@clerk/localizations4.5.3-snapshot.v20260421194054
@clerk/msw0.0.15-snapshot.v20260421194054
@clerk/nextjs7.2.4-snapshot.v20260421194054
@clerk/nuxt2.2.5-snapshot.v20260421194054
@clerk/react6.4.3-snapshot.v20260421194054
@clerk/react-router3.1.4-snapshot.v20260421194054
@clerk/shared4.8.3-snapshot.v20260421194054
@clerk/tanstack-react-start1.1.4-snapshot.v20260421194054
@clerk/testing2.0.18-snapshot.v20260421194054
@clerk/ui1.6.4-snapshot.v20260421194054
@clerk/upgrade2.0.3-snapshot.v20260421194054
@clerk/vue2.0.16-snapshot.v20260421194054

Tip: Use the snippet copy button below to quickly install the required packages.
@clerk/astro

npm i @clerk/astro@3.0.18-snapshot.v20260421194054 --save-exact

@clerk/backend

npm i @clerk/backend@3.2.14-snapshot.v20260421194054 --save-exact

@clerk/chrome-extension

npm i @clerk/chrome-extension@3.1.15-snapshot.v20260421194054 --save-exact

@clerk/clerk-js

npm i @clerk/clerk-js@6.7.5-snapshot.v20260421194054 --save-exact

@clerk/dev-cli

npm i @clerk/dev-cli@0.1.1-snapshot.v20260421194054 --save-exact

@clerk/expo

npm i @clerk/expo@3.2.2-snapshot.v20260421194054 --save-exact

@clerk/expo-passkeys

npm i @clerk/expo-passkeys@1.0.15-snapshot.v20260421194054 --save-exact

@clerk/express

npm i @clerk/express@2.1.6-snapshot.v20260421194054 --save-exact

@clerk/fastify

npm i @clerk/fastify@3.1.16-snapshot.v20260421194054 --save-exact

@clerk/hono

npm i @clerk/hono@0.1.16-snapshot.v20260421194054 --save-exact

@clerk/localizations

npm i @clerk/localizations@4.5.3-snapshot.v20260421194054 --save-exact

@clerk/msw

npm i @clerk/msw@0.0.15-snapshot.v20260421194054 --save-exact

@clerk/nextjs

npm i @clerk/nextjs@7.2.4-snapshot.v20260421194054 --save-exact

@clerk/nuxt

npm i @clerk/nuxt@2.2.5-snapshot.v20260421194054 --save-exact

@clerk/react

npm i @clerk/react@6.4.3-snapshot.v20260421194054 --save-exact

@clerk/react-router

npm i @clerk/react-router@3.1.4-snapshot.v20260421194054 --save-exact

@clerk/shared

npm i @clerk/shared@4.8.3-snapshot.v20260421194054 --save-exact

@clerk/tanstack-react-start

npm i @clerk/tanstack-react-start@1.1.4-snapshot.v20260421194054 --save-exact

@clerk/testing

npm i @clerk/testing@2.0.18-snapshot.v20260421194054 --save-exact

@clerk/ui

npm i @clerk/ui@1.6.4-snapshot.v20260421194054 --save-exact

@clerk/upgrade

npm i @clerk/upgrade@2.0.3-snapshot.v20260421194054 --save-exact

@clerk/vue

npm i @clerk/vue@2.0.16-snapshot.v20260421194054 --save-exact

@Railly

Copy link
Copy Markdown
Contributor

@Ephem Tested with a real production instance on vercel.app, auth works end-to-end.

One finding: the default middleware matcher excludes .js files, which prevents /__clerk/npm/@clerk/clerk-js@6/dist/clerk.browser.js from reaching the middleware. Had to add "/__clerk/(.*)" to the matcher manually. Since auto-proxy is meant to work without config, I think clerkMiddleware should handle this automatically or the default matcher should always include __clerk.

Test app: https://v0-auto-proxy-test.vercel.app

@EphemEphem left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is looking really good, I only worry slightly about the backend case, made a suggestion.

We still need to figure out the /__clerk/(.*) not being in the matcher by default too. Not sure that's going to affect this PR though, but we should probably figure it out before merging.

Comment threadpackages/shared/src/proxy.ts
) {
// Auto-detect proxy for supported platform deployments (production only)
if (!options.proxyUrl && !options.domain && options.publishableKey?.startsWith('pk_live_')) {
const hostname = clerkRequest.clerkUrl.hostname;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure a comment is enough here (it also doesn't explain why this is currently safe). It's very hard to fully understand the implications of something like this, especially as code changes over time, so I think it doesn't hurt to be defensive.

I wonder if we could straight up reuse getAutoProxyUrlFromEnvironment here (with minor tweaks)? That way, if the backend is deployed to Vercel auto-proxying will work correctly, which should be fine for the use case we are targeting?

If the backend is hosted separately, I think requiring you to configure a proxyUrl for it yourself is fair instead of picking it up from the header?

@Railly

Copy link
Copy Markdown
Contributor

I'm not sure a comment is enough here (it also doesn't explain why this is currently safe). It's very hard to fully understand the implications of something like this, especially as code changes over time, so I think it doesn't hurt to be defensive.
I wonder if we could straight up reuse getAutoProxyUrlFromEnvironment here (with minor tweaks)? That way, if the backend is deployed to Vercel auto-proxying will work correctly, which should be fine for the use case we are targeting?
If the backend is hosted separately, I think requiring you to configure a proxyUrl for it yourself is fair instead of picking it up from the header?

@Ephem addressed in 43be86f, replaced header-based detection with getAutoProxyUrlFromEnvironment in the backend. If deployed outside Vercel, auto-proxy simply doesn't activate and you configure proxyUrl manually.

@EphemEphem left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Ephem addressed in 43be86f, replaced header-based detection with getAutoProxyUrlFromEnvironment in the backend. If deployed outside Vercel, auto-proxy simply doesn't activate and you configure proxyUrl manually.

Awesome, that sidesteps the concern entirely, thanks!

I think this is looking good to go now, great work Bryce on the original implementation and Railly on picking it up!

@nikosdouvlis Do you have any final concerns?

@nikosdouvlisnikosdouvlis left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM :)

@Railly

Copy link
Copy Markdown
Contributor

hey @nikosdouvlis, could you merge this when you get a chance? I’m not the PR author so I don’t have permissions

@nikosdouvlis
nikosdouvlis merged commit 9b57986 into mainApr 28, 2026
177 of 192 checks passed
@nikosdouvlis
nikosdouvlis deleted the brkalow/auto-proxy-vercel-subdomains branch April 28, 2026 21:10
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@brkalow@dominic-clerk@Railly@nikosdouvlis@Ephem
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

feat(*): auto-proxy for eligible hosts - #8035

Merged
nikosdouvlis merged 35 commits into
mainfrom
brkalow/auto-proxy-vercel-subdomains
Apr 28, 2026
Merged

feat(*): auto-proxy for eligible hosts#8035
nikosdouvlis merged 35 commits into
mainfrom
brkalow/auto-proxy-vercel-subdomains

Conversation

@brkalow

@brkalowbrkalow commented Mar 11, 2026

Copy link
Copy Markdown
Member

Description

This PR implements automatic FAPI proxying for Vercel preview deployments. When an app is deployed to a .vercel.app subdomain without explicit proxy or domain configuration, the SDK automatically routes FAPI requests through the app's own domain via the /__clerk proxy path.

The auto-detection is centralized across all SDKs:

  • Shared utilities (@clerk/shared/proxy): New isVercelPreviewDeploy() helper detects .vercel.app hostnames
  • Client-side (@clerk/clerk-js): Auto-detect in proxyUrl getter when window.location.hostname is .vercel.app
  • Server-side (@clerk/backend): Auto-detect in authenticateContext constructor when request hostname is .vercel.app
  • Next.js middleware (@clerk/nextjs): Auto-enable proxy interception for /__clerk/* requests on .vercel.app hostnames

Guard conditions prevent auto-detection when explicit configuration is provided: proxyUrl, domain, or environment variables NEXT_PUBLIC_CLERK_PROXY_URL / NEXT_PUBLIC_CLERK_DOMAIN.

Test Results

  • @clerk/shared: 981/981 tests passed
  • @clerk/backend: 1114/1114 tests passed
  • @clerk/clerk-js: 624/624 tests passed
  • @clerk/nextjs: 362 tests passed (49 pre-existing failures unrelated to this change)

Build verification: All packages build cleanly with no type errors.

Checklist

  • pnpm test runs as expected
  • pnpm build runs as expected
  • Tests added for new functionality

Type of change

  • 🌟 New feature

Summary by CodeRabbit

  • New Features

    • Automatic proxy detection for Vercel-style hosts: when no explicit proxy or domain is provided, the frontend API routing can auto-configure a relative proxy for eligible deployments; explicit proxy or domain settings take precedence.
  • Bug Fixes

    • Improved handling of relative proxy URLs in non-browser/server contexts and script-host resolution fallback.
  • Tests

    • Added comprehensive tests validating auto-detection, precedence rules, environment-based behavior, and various host scenarios.

@vercel

vercelBot commented Mar 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentApr 27, 2026 7:48pm

Request Review

@changeset-bot

changeset-botBot commented Mar 11, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: d0450ed

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 20 packages
NameType
@clerk/sharedPatch
@clerk/backendPatch
@clerk/clerk-jsPatch
@clerk/nextjsPatch
@clerk/astroPatch
@clerk/chrome-extensionPatch
@clerk/expo-passkeysPatch
@clerk/expoPatch
@clerk/expressPatch
@clerk/fastifyPatch
@clerk/honoPatch
@clerk/localizationsPatch
@clerk/mswPatch
@clerk/nuxtPatch
@clerk/react-routerPatch
@clerk/reactPatch
@clerk/tanstack-react-startPatch
@clerk/testingPatch
@clerk/uiPatch
@clerk/vuePatch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitaiBot commented Mar 11, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds exported functions shouldAutoProxy(hostname: string) and getAutoProxyUrlFromEnvironment(...) to the shared package to detect eligible hostnames (e.g., *.vercel.app) and derive a relative proxy path (/__clerk). Integrates this auto-proxy logic into backend AuthenticateContext, clerk-js core, Next.js middleware, and environment-merging utilities so that when neither proxyUrl nor domain is provided, the proxy may be set to the current origin + /__clerk for eligible environments. Adds tests across packages and a changeset entry.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 20.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title 'feat(*): auto-proxy for eligible hosts' accurately describes the main feature being implemented across the codebase - automatic proxy detection for eligible hostnames (specifically .vercel.app subdomains).

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

…omains
Add automatic FAPI proxying detection for Vercel preview deployments across all SDKs. When an app is deployed to a .vercel.app subdomain without explicit proxy/domain configuration, the SDK automatically routes FAPI requests through the app's own domain via /__clerk proxy path.
- Add isVercelPreviewDeploy() helper in @clerk/shared/proxy
- Auto-detect in clerk-js proxyUrl getter for client-side SDK initialization
- Auto-detect in @clerk/backend authenticateContext for server-side auth
- Enable proxy interception in Next.js middleware for /__clerk/* requests on .vercel.app
- Add comprehensive tests for all three layers (shared, backend, clerk-js, nextjs)
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
@pkg-pr-new

pkg-pr-newBot commented Mar 13, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

npm i https://pkg.pr.new/@clerk/astro@8035

@clerk/backend

npm i https://pkg.pr.new/@clerk/backend@8035

@clerk/chrome-extension

npm i https://pkg.pr.new/@clerk/chrome-extension@8035

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@8035

@clerk/dev-cli

npm i https://pkg.pr.new/@clerk/dev-cli@8035

@clerk/expo

npm i https://pkg.pr.new/@clerk/expo@8035

@clerk/expo-passkeys

npm i https://pkg.pr.new/@clerk/expo-passkeys@8035

@clerk/express

npm i https://pkg.pr.new/@clerk/express@8035

@clerk/fastify

npm i https://pkg.pr.new/@clerk/fastify@8035

@clerk/hono

npm i https://pkg.pr.new/@clerk/hono@8035

@clerk/localizations

npm i https://pkg.pr.new/@clerk/localizations@8035

@clerk/nextjs

npm i https://pkg.pr.new/@clerk/nextjs@8035

@clerk/nuxt

npm i https://pkg.pr.new/@clerk/nuxt@8035

@clerk/react

npm i https://pkg.pr.new/@clerk/react@8035

@clerk/react-router

npm i https://pkg.pr.new/@clerk/react-router@8035

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@8035

@clerk/tanstack-react-start

npm i https://pkg.pr.new/@clerk/tanstack-react-start@8035

@clerk/testing

npm i https://pkg.pr.new/@clerk/testing@8035

@clerk/ui

npm i https://pkg.pr.new/@clerk/ui@8035

@clerk/upgrade

npm i https://pkg.pr.new/@clerk/upgrade@8035

@clerk/vue

npm i https://pkg.pr.new/@clerk/vue@8035

commit: d0450ed

@brkalowbrkalow changed the title Auto-proxy for .vercel.app subdomainsfeat(*): auto-proxy for eligible hostsMar 13, 2026

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.changeset/tiny-badgers-smile.md:
- Line 8: Update the changeset description sentence that currently reads "Add
auto-proxy detection for eligible hosts and generalize the internal helper
naming for future providers." to be specific about which hosts are considered
eligible (e.g., Vercel preview deployments) and to clarify the helper change;
for example, change the description to: "Add auto-proxy detection for Vercel
preview deployments (.vercel.app subdomains) and generalize the internal helper
for future hosting providers." Locate and edit the line containing that
description in the changeset (the sentence beginning "Add auto-proxy detection
for eligible hosts...") and replace it with the clearer, user-facing wording.
- Around line 1-6: The changeset incorrectly marks package bumps as 'patch' but
a new public API (shouldAutoProxy) was added; update the entries so any package
that exports the new function (at least '@clerk/shared', and any packages that
re-export it like '@clerk/backend', '@clerk/clerk-js', '@clerk/nextjs' if
applicable) use 'minor' instead of 'patch' in the
.changeset/tiny-badgers-smile.md file; ensure the header lines for those package
entries read 'minor' to reflect the new public API bump while keeping other
metadata unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0cecde51-9659-43c2-8677-37774a033a71

📥 Commits

Reviewing files that changed from the base of the PR and between dbc580e and 2d729d2.

📒 Files selected for processing (1)
  • .changeset/tiny-badgers-smile.md

Comment thread.changeset/tiny-badgers-smile.md Outdated
Comment on lines +1 to +6
---
'@clerk/backend': patch
'@clerk/clerk-js': patch
'@clerk/nextjs': patch
'@clerk/shared': patch
---

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash# Description: Check if shouldAutoProxy is a public export# Search for shouldAutoProxy exports in the shared package
rg -n "export.*shouldAutoProxy" packages/shared/

Repository: clerk/javascript

Length of output: 152


Update version bumps from patch to minor to reflect the new public API export.

The changeset marks all packages for patch bumps, but the PR introduces a new public API function shouldAutoProxy exported from @clerk/shared. Per semantic versioning standards, new public APIs require minor version bumps, not patch (which is reserved for bug fixes). Update the changeset accordingly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.changeset/tiny-badgers-smile.md around lines 1 - 6, The changeset
incorrectly marks package bumps as 'patch' but a new public API
(shouldAutoProxy) was added; update the entries so any package that exports the
new function (at least '@clerk/shared', and any packages that re-export it like
'@clerk/backend', '@clerk/clerk-js', '@clerk/nextjs' if applicable) use 'minor'
instead of 'patch' in the .changeset/tiny-badgers-smile.md file; ensure the
header lines for those package entries read 'minor' to reflect the new public
API bump while keeping other metadata unchanged.

Comment thread.changeset/tiny-badgers-smile.md Outdated
@brkalow

Copy link
Copy Markdown
MemberAuthor

!snapshot

@github-actions

This comment has been minimized.

@brkalow

Copy link
Copy Markdown
MemberAuthor

!snapshot

@github-actions

This comment has been minimized.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/shared/src/proxy.ts`:
- Around line 45-47: The normalizeHostname function can throw when called with a
malformed URL (e.g., VERCEL_PROJECT_PRODUCTION_URL); wrap the URL parsing in a
try-catch inside normalizeHostname (the function shown using new
URL(...).hostname) and return an empty string on error so config resolution
doesn't crash; ensure callers that rely on normalizeHostname (where
VERCEL_PROJECT_PRODUCTION_URL is passed) can handle an empty string result
gracefully.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: 2ad9a329-ea13-4ffe-86f9-f7dec3b163e7

📥 Commits

Reviewing files that changed from the base of the PR and between 2d729d2 and 72d6bdc.

📒 Files selected for processing (5)
  • .changeset/tiny-badgers-smile.md
  • packages/nextjs/src/utils/__tests__/mergeNextClerkPropsWithEnv.test.ts
  • packages/nextjs/src/utils/mergeNextClerkPropsWithEnv.ts
  • packages/shared/src/__tests__/proxy.spec.ts
  • packages/shared/src/proxy.ts

Comment threadpackages/shared/src/proxy.ts Outdated
Comment on lines +45 to +47
function normalizeHostname(hostnameOrUrl: string): string {
if (hostnameOrUrl.startsWith('http://') || hostnameOrUrl.startsWith('https://')) {
return new URL(hostnameOrUrl).hostname;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash# Verify call path and URL constructor throw behavior
rg -n "normalizeHostname|getAutoProxyUrlFromEnvironment|VERCEL_PROJECT_PRODUCTION_URL" \
packages/shared/src/proxy.ts packages/nextjs/src/utils/mergeNextClerkPropsWithEnv.ts
node - <<'NODE'const samples = ['https://', 'https://?x=1', 'https:///foo', 'https://myapp.vercel.app'];for (const s of samples) { try { console.log(`${s} => hostname="${new URL(s).hostname}"`); } catch (e) { console.log(`${s} => THROWS (${e.name})`); }}NODE

Repository: clerk/javascript

Length of output: 853


🏁 Script executed:

#!/bin/bash# Get full function context at line 45-47 and surrounding linesecho"=== normalizeHostname function context (lines 41-55) ==="
sed -n '41,55p' packages/shared/src/proxy.ts | cat -n
echo -e "\n=== getAutoProxyUrlFromEnvironment call context (lines 70-80) ==="
sed -n '70,80p' packages/shared/src/proxy.ts | cat -n
# Search for tests related to proxy.ts or normalizeHostnameecho -e "\n=== Tests for proxy functions ==="
find . -name "*.test.ts" -o -name "*.spec.ts"| xargs grep -l "normalizeHostname\|getAutoProxyUrlFromEnvironment\|proxy"2>/dev/null | head -10

Repository: clerk/javascript

Length of output: 1707


🏁 Script executed:

#!/bin/bash# Check if there are tests covering error cases for these functionsecho"=== Searching for test files related to proxy ==="
fd -e "test.ts" -e "spec.ts"| xargs grep -l "normalizeHostname\|getAutoProxyUrlFromEnvironment"2>/dev/null
# Also check the test directory structureecho -e "\n=== Checking __tests__ and test directory structure ==="
find packages/shared -type d \( -name "__tests__" -o -name "tests" -o -name "test"\)| head -5

Repository: clerk/javascript

Length of output: 410


🏁 Script executed:

#!/bin/bash# Read the existing proxy test file to see current coverageecho"=== Existing proxy tests ==="
cat packages/shared/src/__tests__/proxy.spec.ts

Repository: clerk/javascript

Length of output: 5211


Handle malformed VERCEL_PROJECT_PRODUCTION_URL in hostname normalization to prevent runtime crashes.

At line 76, normalizeHostname() is called with an environment variable without error handling. If VERCEL_PROJECT_PRODUCTION_URL is malformed (e.g., https:// or https://?x=1), new URL() throws a TypeError, crashing config resolution instead of gracefully returning an empty string. Add try-catch to safely handle this:

Proposed fix
function normalizeHostname(hostnameOrUrl: string): string {
if (hostnameOrUrl.startsWith('http://') || hostnameOrUrl.startsWith('https://')) {
- return new URL(hostnameOrUrl).hostname;+ try {+ return new URL(hostnameOrUrl).hostname;+ } catch {+ return '';+ }
}
return hostnameOrUrl.split('/')[0] || '';
}

Also applies to: 76-77

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/shared/src/proxy.ts` around lines 45 - 47, The normalizeHostname
function can throw when called with a malformed URL (e.g.,
VERCEL_PROJECT_PRODUCTION_URL); wrap the URL parsing in a try-catch inside
normalizeHostname (the function shown using new URL(...).hostname) and return an
empty string on error so config resolution doesn't crash; ensure callers that
rely on normalizeHostname (where VERCEL_PROJECT_PRODUCTION_URL is passed) can
handle an empty string result gracefully.

@brkalow

Copy link
Copy Markdown
MemberAuthor

!snapshot

@github-actions

This comment has been minimized.

@Railly

Copy link
Copy Markdown
Contributor

!snapshot

@github-actions

Copy link
Copy Markdown
Contributor

Hey @Railly - the snapshot version command generated the following package versions:

PackageVersion
@clerk/astro3.0.18-snapshot.v20260421194054
@clerk/backend3.2.14-snapshot.v20260421194054
@clerk/chrome-extension3.1.15-snapshot.v20260421194054
@clerk/clerk-js6.7.5-snapshot.v20260421194054
@clerk/dev-cli0.1.1-snapshot.v20260421194054
@clerk/expo3.2.2-snapshot.v20260421194054
@clerk/expo-passkeys1.0.15-snapshot.v20260421194054
@clerk/express2.1.6-snapshot.v20260421194054
@clerk/fastify3.1.16-snapshot.v20260421194054
@clerk/hono0.1.16-snapshot.v20260421194054
@clerk/localizations4.5.3-snapshot.v20260421194054
@clerk/msw0.0.15-snapshot.v20260421194054
@clerk/nextjs7.2.4-snapshot.v20260421194054
@clerk/nuxt2.2.5-snapshot.v20260421194054
@clerk/react6.4.3-snapshot.v20260421194054
@clerk/react-router3.1.4-snapshot.v20260421194054
@clerk/shared4.8.3-snapshot.v20260421194054
@clerk/tanstack-react-start1.1.4-snapshot.v20260421194054
@clerk/testing2.0.18-snapshot.v20260421194054
@clerk/ui1.6.4-snapshot.v20260421194054
@clerk/upgrade2.0.3-snapshot.v20260421194054
@clerk/vue2.0.16-snapshot.v20260421194054

Tip: Use the snippet copy button below to quickly install the required packages.
@clerk/astro

npm i @clerk/astro@3.0.18-snapshot.v20260421194054 --save-exact

@clerk/backend

npm i @clerk/backend@3.2.14-snapshot.v20260421194054 --save-exact

@clerk/chrome-extension

npm i @clerk/chrome-extension@3.1.15-snapshot.v20260421194054 --save-exact

@clerk/clerk-js

npm i @clerk/clerk-js@6.7.5-snapshot.v20260421194054 --save-exact

@clerk/dev-cli

npm i @clerk/dev-cli@0.1.1-snapshot.v20260421194054 --save-exact

@clerk/expo

npm i @clerk/expo@3.2.2-snapshot.v20260421194054 --save-exact

@clerk/expo-passkeys

npm i @clerk/expo-passkeys@1.0.15-snapshot.v20260421194054 --save-exact

@clerk/express

npm i @clerk/express@2.1.6-snapshot.v20260421194054 --save-exact

@clerk/fastify

npm i @clerk/fastify@3.1.16-snapshot.v20260421194054 --save-exact

@clerk/hono

npm i @clerk/hono@0.1.16-snapshot.v20260421194054 --save-exact

@clerk/localizations

npm i @clerk/localizations@4.5.3-snapshot.v20260421194054 --save-exact

@clerk/msw

npm i @clerk/msw@0.0.15-snapshot.v20260421194054 --save-exact

@clerk/nextjs

npm i @clerk/nextjs@7.2.4-snapshot.v20260421194054 --save-exact

@clerk/nuxt

npm i @clerk/nuxt@2.2.5-snapshot.v20260421194054 --save-exact

@clerk/react

npm i @clerk/react@6.4.3-snapshot.v20260421194054 --save-exact

@clerk/react-router

npm i @clerk/react-router@3.1.4-snapshot.v20260421194054 --save-exact

@clerk/shared

npm i @clerk/shared@4.8.3-snapshot.v20260421194054 --save-exact

@clerk/tanstack-react-start

npm i @clerk/tanstack-react-start@1.1.4-snapshot.v20260421194054 --save-exact

@clerk/testing

npm i @clerk/testing@2.0.18-snapshot.v20260421194054 --save-exact

@clerk/ui

npm i @clerk/ui@1.6.4-snapshot.v20260421194054 --save-exact

@clerk/upgrade

npm i @clerk/upgrade@2.0.3-snapshot.v20260421194054 --save-exact

@clerk/vue

npm i @clerk/vue@2.0.16-snapshot.v20260421194054 --save-exact

@Railly

Copy link
Copy Markdown
Contributor

@Ephem Tested with a real production instance on vercel.app, auth works end-to-end.

One finding: the default middleware matcher excludes .js files, which prevents /__clerk/npm/@clerk/clerk-js@6/dist/clerk.browser.js from reaching the middleware. Had to add "/__clerk/(.*)" to the matcher manually. Since auto-proxy is meant to work without config, I think clerkMiddleware should handle this automatically or the default matcher should always include __clerk.

Test app: https://v0-auto-proxy-test.vercel.app

@EphemEphem left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is looking really good, I only worry slightly about the backend case, made a suggestion.

We still need to figure out the /__clerk/(.*) not being in the matcher by default too. Not sure that's going to affect this PR though, but we should probably figure it out before merging.

Comment threadpackages/shared/src/proxy.ts
) {
// Auto-detect proxy for supported platform deployments (production only)
if (!options.proxyUrl && !options.domain && options.publishableKey?.startsWith('pk_live_')) {
const hostname = clerkRequest.clerkUrl.hostname;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure a comment is enough here (it also doesn't explain why this is currently safe). It's very hard to fully understand the implications of something like this, especially as code changes over time, so I think it doesn't hurt to be defensive.

I wonder if we could straight up reuse getAutoProxyUrlFromEnvironment here (with minor tweaks)? That way, if the backend is deployed to Vercel auto-proxying will work correctly, which should be fine for the use case we are targeting?

If the backend is hosted separately, I think requiring you to configure a proxyUrl for it yourself is fair instead of picking it up from the header?

@Railly

Copy link
Copy Markdown
Contributor

I'm not sure a comment is enough here (it also doesn't explain why this is currently safe). It's very hard to fully understand the implications of something like this, especially as code changes over time, so I think it doesn't hurt to be defensive.
I wonder if we could straight up reuse getAutoProxyUrlFromEnvironment here (with minor tweaks)? That way, if the backend is deployed to Vercel auto-proxying will work correctly, which should be fine for the use case we are targeting?
If the backend is hosted separately, I think requiring you to configure a proxyUrl for it yourself is fair instead of picking it up from the header?

@Ephem addressed in 43be86f, replaced header-based detection with getAutoProxyUrlFromEnvironment in the backend. If deployed outside Vercel, auto-proxy simply doesn't activate and you configure proxyUrl manually.

@EphemEphem left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Ephem addressed in 43be86f, replaced header-based detection with getAutoProxyUrlFromEnvironment in the backend. If deployed outside Vercel, auto-proxy simply doesn't activate and you configure proxyUrl manually.

Awesome, that sidesteps the concern entirely, thanks!

I think this is looking good to go now, great work Bryce on the original implementation and Railly on picking it up!

@nikosdouvlis Do you have any final concerns?

@nikosdouvlisnikosdouvlis left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM :)

@Railly

Copy link
Copy Markdown
Contributor

hey @nikosdouvlis, could you merge this when you get a chance? I’m not the PR author so I don’t have permissions

@nikosdouvlis
nikosdouvlis merged commit 9b57986 into mainApr 28, 2026
177 of 192 checks passed
@nikosdouvlis
nikosdouvlis deleted the brkalow/auto-proxy-vercel-subdomains branch April 28, 2026 21:10
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@brkalow@dominic-clerk@Railly@nikosdouvlis@Ephem
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(*): auto-proxy for eligible hosts - #8035

Merged
nikosdouvlis merged 35 commits into
mainfrom
brkalow/auto-proxy-vercel-subdomains
Apr 28, 2026
Merged

feat(*): auto-proxy for eligible hosts#8035
nikosdouvlis merged 35 commits into
mainfrom
brkalow/auto-proxy-vercel-subdomains

Conversation

@brkalow

@brkalowbrkalow commented Mar 11, 2026

Copy link
Copy Markdown
Member

Description

This PR implements automatic FAPI proxying for Vercel preview deployments. When an app is deployed to a .vercel.app subdomain without explicit proxy or domain configuration, the SDK automatically routes FAPI requests through the app's own domain via the /__clerk proxy path.

The auto-detection is centralized across all SDKs:

  • Shared utilities (@clerk/shared/proxy): New isVercelPreviewDeploy() helper detects .vercel.app hostnames
  • Client-side (@clerk/clerk-js): Auto-detect in proxyUrl getter when window.location.hostname is .vercel.app
  • Server-side (@clerk/backend): Auto-detect in authenticateContext constructor when request hostname is .vercel.app
  • Next.js middleware (@clerk/nextjs): Auto-enable proxy interception for /__clerk/* requests on .vercel.app hostnames

Guard conditions prevent auto-detection when explicit configuration is provided: proxyUrl, domain, or environment variables NEXT_PUBLIC_CLERK_PROXY_URL / NEXT_PUBLIC_CLERK_DOMAIN.

Test Results

  • @clerk/shared: 981/981 tests passed
  • @clerk/backend: 1114/1114 tests passed
  • @clerk/clerk-js: 624/624 tests passed
  • @clerk/nextjs: 362 tests passed (49 pre-existing failures unrelated to this change)

Build verification: All packages build cleanly with no type errors.

Checklist

  • pnpm test runs as expected
  • pnpm build runs as expected
  • Tests added for new functionality

Type of change

  • 🌟 New feature

Summary by CodeRabbit

  • New Features

    • Automatic proxy detection for Vercel-style hosts: when no explicit proxy or domain is provided, the frontend API routing can auto-configure a relative proxy for eligible deployments; explicit proxy or domain settings take precedence.
  • Bug Fixes

    • Improved handling of relative proxy URLs in non-browser/server contexts and script-host resolution fallback.
  • Tests

    • Added comprehensive tests validating auto-detection, precedence rules, environment-based behavior, and various host scenarios.

@vercel

vercelBot commented Mar 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentApr 27, 2026 7:48pm

Request Review

@changeset-bot

changeset-botBot commented Mar 11, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: d0450ed

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 20 packages
NameType
@clerk/sharedPatch
@clerk/backendPatch
@clerk/clerk-jsPatch
@clerk/nextjsPatch
@clerk/astroPatch
@clerk/chrome-extensionPatch
@clerk/expo-passkeysPatch
@clerk/expoPatch
@clerk/expressPatch
@clerk/fastifyPatch
@clerk/honoPatch
@clerk/localizationsPatch
@clerk/mswPatch
@clerk/nuxtPatch
@clerk/react-routerPatch
@clerk/reactPatch
@clerk/tanstack-react-startPatch
@clerk/testingPatch
@clerk/uiPatch
@clerk/vuePatch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitaiBot commented Mar 11, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds exported functions shouldAutoProxy(hostname: string) and getAutoProxyUrlFromEnvironment(...) to the shared package to detect eligible hostnames (e.g., *.vercel.app) and derive a relative proxy path (/__clerk). Integrates this auto-proxy logic into backend AuthenticateContext, clerk-js core, Next.js middleware, and environment-merging utilities so that when neither proxyUrl nor domain is provided, the proxy may be set to the current origin + /__clerk for eligible environments. Adds tests across packages and a changeset entry.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 20.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title 'feat(*): auto-proxy for eligible hosts' accurately describes the main feature being implemented across the codebase - automatic proxy detection for eligible hostnames (specifically .vercel.app subdomains).

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

…omains
Add automatic FAPI proxying detection for Vercel preview deployments across all SDKs. When an app is deployed to a .vercel.app subdomain without explicit proxy/domain configuration, the SDK automatically routes FAPI requests through the app's own domain via /__clerk proxy path.
- Add isVercelPreviewDeploy() helper in @clerk/shared/proxy
- Auto-detect in clerk-js proxyUrl getter for client-side SDK initialization
- Auto-detect in @clerk/backend authenticateContext for server-side auth
- Enable proxy interception in Next.js middleware for /__clerk/* requests on .vercel.app
- Add comprehensive tests for all three layers (shared, backend, clerk-js, nextjs)
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
@pkg-pr-new

pkg-pr-newBot commented Mar 13, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

npm i https://pkg.pr.new/@clerk/astro@8035

@clerk/backend

npm i https://pkg.pr.new/@clerk/backend@8035

@clerk/chrome-extension

npm i https://pkg.pr.new/@clerk/chrome-extension@8035

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@8035

@clerk/dev-cli

npm i https://pkg.pr.new/@clerk/dev-cli@8035

@clerk/expo

npm i https://pkg.pr.new/@clerk/expo@8035

@clerk/expo-passkeys

npm i https://pkg.pr.new/@clerk/expo-passkeys@8035

@clerk/express

npm i https://pkg.pr.new/@clerk/express@8035

@clerk/fastify

npm i https://pkg.pr.new/@clerk/fastify@8035

@clerk/hono

npm i https://pkg.pr.new/@clerk/hono@8035

@clerk/localizations

npm i https://pkg.pr.new/@clerk/localizations@8035

@clerk/nextjs

npm i https://pkg.pr.new/@clerk/nextjs@8035

@clerk/nuxt

npm i https://pkg.pr.new/@clerk/nuxt@8035

@clerk/react

npm i https://pkg.pr.new/@clerk/react@8035

@clerk/react-router

npm i https://pkg.pr.new/@clerk/react-router@8035

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@8035

@clerk/tanstack-react-start

npm i https://pkg.pr.new/@clerk/tanstack-react-start@8035

@clerk/testing

npm i https://pkg.pr.new/@clerk/testing@8035

@clerk/ui

npm i https://pkg.pr.new/@clerk/ui@8035

@clerk/upgrade

npm i https://pkg.pr.new/@clerk/upgrade@8035

@clerk/vue

npm i https://pkg.pr.new/@clerk/vue@8035

commit: d0450ed

@brkalowbrkalow changed the title Auto-proxy for .vercel.app subdomainsfeat(*): auto-proxy for eligible hostsMar 13, 2026

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.changeset/tiny-badgers-smile.md:
- Line 8: Update the changeset description sentence that currently reads "Add
auto-proxy detection for eligible hosts and generalize the internal helper
naming for future providers." to be specific about which hosts are considered
eligible (e.g., Vercel preview deployments) and to clarify the helper change;
for example, change the description to: "Add auto-proxy detection for Vercel
preview deployments (.vercel.app subdomains) and generalize the internal helper
for future hosting providers." Locate and edit the line containing that
description in the changeset (the sentence beginning "Add auto-proxy detection
for eligible hosts...") and replace it with the clearer, user-facing wording.
- Around line 1-6: The changeset incorrectly marks package bumps as 'patch' but
a new public API (shouldAutoProxy) was added; update the entries so any package
that exports the new function (at least '@clerk/shared', and any packages that
re-export it like '@clerk/backend', '@clerk/clerk-js', '@clerk/nextjs' if
applicable) use 'minor' instead of 'patch' in the
.changeset/tiny-badgers-smile.md file; ensure the header lines for those package
entries read 'minor' to reflect the new public API bump while keeping other
metadata unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0cecde51-9659-43c2-8677-37774a033a71

📥 Commits

Reviewing files that changed from the base of the PR and between dbc580e and 2d729d2.

📒 Files selected for processing (1)
  • .changeset/tiny-badgers-smile.md

Comment thread.changeset/tiny-badgers-smile.md Outdated
Comment on lines +1 to +6
---
'@clerk/backend': patch
'@clerk/clerk-js': patch
'@clerk/nextjs': patch
'@clerk/shared': patch
---

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash# Description: Check if shouldAutoProxy is a public export# Search for shouldAutoProxy exports in the shared package
rg -n "export.*shouldAutoProxy" packages/shared/

Repository: clerk/javascript

Length of output: 152


Update version bumps from patch to minor to reflect the new public API export.

The changeset marks all packages for patch bumps, but the PR introduces a new public API function shouldAutoProxy exported from @clerk/shared. Per semantic versioning standards, new public APIs require minor version bumps, not patch (which is reserved for bug fixes). Update the changeset accordingly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.changeset/tiny-badgers-smile.md around lines 1 - 6, The changeset
incorrectly marks package bumps as 'patch' but a new public API
(shouldAutoProxy) was added; update the entries so any package that exports the
new function (at least '@clerk/shared', and any packages that re-export it like
'@clerk/backend', '@clerk/clerk-js', '@clerk/nextjs' if applicable) use 'minor'
instead of 'patch' in the .changeset/tiny-badgers-smile.md file; ensure the
header lines for those package entries read 'minor' to reflect the new public
API bump while keeping other metadata unchanged.

Comment thread.changeset/tiny-badgers-smile.md Outdated
@brkalow

Copy link
Copy Markdown
MemberAuthor

!snapshot

@github-actions

This comment has been minimized.

@brkalow

Copy link
Copy Markdown
MemberAuthor

!snapshot

@github-actions

This comment has been minimized.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/shared/src/proxy.ts`:
- Around line 45-47: The normalizeHostname function can throw when called with a
malformed URL (e.g., VERCEL_PROJECT_PRODUCTION_URL); wrap the URL parsing in a
try-catch inside normalizeHostname (the function shown using new
URL(...).hostname) and return an empty string on error so config resolution
doesn't crash; ensure callers that rely on normalizeHostname (where
VERCEL_PROJECT_PRODUCTION_URL is passed) can handle an empty string result
gracefully.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: 2ad9a329-ea13-4ffe-86f9-f7dec3b163e7

📥 Commits

Reviewing files that changed from the base of the PR and between 2d729d2 and 72d6bdc.

📒 Files selected for processing (5)
  • .changeset/tiny-badgers-smile.md
  • packages/nextjs/src/utils/__tests__/mergeNextClerkPropsWithEnv.test.ts
  • packages/nextjs/src/utils/mergeNextClerkPropsWithEnv.ts
  • packages/shared/src/__tests__/proxy.spec.ts
  • packages/shared/src/proxy.ts

Comment threadpackages/shared/src/proxy.ts Outdated
Comment on lines +45 to +47
function normalizeHostname(hostnameOrUrl: string): string {
if (hostnameOrUrl.startsWith('http://') || hostnameOrUrl.startsWith('https://')) {
return new URL(hostnameOrUrl).hostname;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash# Verify call path and URL constructor throw behavior
rg -n "normalizeHostname|getAutoProxyUrlFromEnvironment|VERCEL_PROJECT_PRODUCTION_URL" \
packages/shared/src/proxy.ts packages/nextjs/src/utils/mergeNextClerkPropsWithEnv.ts
node - <<'NODE'const samples = ['https://', 'https://?x=1', 'https:///foo', 'https://myapp.vercel.app'];for (const s of samples) { try { console.log(`${s} => hostname="${new URL(s).hostname}"`); } catch (e) { console.log(`${s} => THROWS (${e.name})`); }}NODE

Repository: clerk/javascript

Length of output: 853


🏁 Script executed:

#!/bin/bash# Get full function context at line 45-47 and surrounding linesecho"=== normalizeHostname function context (lines 41-55) ==="
sed -n '41,55p' packages/shared/src/proxy.ts | cat -n
echo -e "\n=== getAutoProxyUrlFromEnvironment call context (lines 70-80) ==="
sed -n '70,80p' packages/shared/src/proxy.ts | cat -n
# Search for tests related to proxy.ts or normalizeHostnameecho -e "\n=== Tests for proxy functions ==="
find . -name "*.test.ts" -o -name "*.spec.ts"| xargs grep -l "normalizeHostname\|getAutoProxyUrlFromEnvironment\|proxy"2>/dev/null | head -10

Repository: clerk/javascript

Length of output: 1707


🏁 Script executed:

#!/bin/bash# Check if there are tests covering error cases for these functionsecho"=== Searching for test files related to proxy ==="
fd -e "test.ts" -e "spec.ts"| xargs grep -l "normalizeHostname\|getAutoProxyUrlFromEnvironment"2>/dev/null
# Also check the test directory structureecho -e "\n=== Checking __tests__ and test directory structure ==="
find packages/shared -type d \( -name "__tests__" -o -name "tests" -o -name "test"\)| head -5

Repository: clerk/javascript

Length of output: 410


🏁 Script executed:

#!/bin/bash# Read the existing proxy test file to see current coverageecho"=== Existing proxy tests ==="
cat packages/shared/src/__tests__/proxy.spec.ts

Repository: clerk/javascript

Length of output: 5211


Handle malformed VERCEL_PROJECT_PRODUCTION_URL in hostname normalization to prevent runtime crashes.

At line 76, normalizeHostname() is called with an environment variable without error handling. If VERCEL_PROJECT_PRODUCTION_URL is malformed (e.g., https:// or https://?x=1), new URL() throws a TypeError, crashing config resolution instead of gracefully returning an empty string. Add try-catch to safely handle this:

Proposed fix
function normalizeHostname(hostnameOrUrl: string): string {
if (hostnameOrUrl.startsWith('http://') || hostnameOrUrl.startsWith('https://')) {
- return new URL(hostnameOrUrl).hostname;+ try {+ return new URL(hostnameOrUrl).hostname;+ } catch {+ return '';+ }
}
return hostnameOrUrl.split('/')[0] || '';
}

Also applies to: 76-77

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/shared/src/proxy.ts` around lines 45 - 47, The normalizeHostname
function can throw when called with a malformed URL (e.g.,
VERCEL_PROJECT_PRODUCTION_URL); wrap the URL parsing in a try-catch inside
normalizeHostname (the function shown using new URL(...).hostname) and return an
empty string on error so config resolution doesn't crash; ensure callers that
rely on normalizeHostname (where VERCEL_PROJECT_PRODUCTION_URL is passed) can
handle an empty string result gracefully.

@brkalow

Copy link
Copy Markdown
MemberAuthor

!snapshot

@github-actions

This comment has been minimized.

@Railly

Copy link
Copy Markdown
Contributor

!snapshot

@github-actions

Copy link
Copy Markdown
Contributor

Hey @Railly - the snapshot version command generated the following package versions:

PackageVersion
@clerk/astro3.0.18-snapshot.v20260421194054
@clerk/backend3.2.14-snapshot.v20260421194054
@clerk/chrome-extension3.1.15-snapshot.v20260421194054
@clerk/clerk-js6.7.5-snapshot.v20260421194054
@clerk/dev-cli0.1.1-snapshot.v20260421194054
@clerk/expo3.2.2-snapshot.v20260421194054
@clerk/expo-passkeys1.0.15-snapshot.v20260421194054
@clerk/express2.1.6-snapshot.v20260421194054
@clerk/fastify3.1.16-snapshot.v20260421194054
@clerk/hono0.1.16-snapshot.v20260421194054
@clerk/localizations4.5.3-snapshot.v20260421194054
@clerk/msw0.0.15-snapshot.v20260421194054
@clerk/nextjs7.2.4-snapshot.v20260421194054
@clerk/nuxt2.2.5-snapshot.v20260421194054
@clerk/react6.4.3-snapshot.v20260421194054
@clerk/react-router3.1.4-snapshot.v20260421194054
@clerk/shared4.8.3-snapshot.v20260421194054
@clerk/tanstack-react-start1.1.4-snapshot.v20260421194054
@clerk/testing2.0.18-snapshot.v20260421194054
@clerk/ui1.6.4-snapshot.v20260421194054
@clerk/upgrade2.0.3-snapshot.v20260421194054
@clerk/vue2.0.16-snapshot.v20260421194054

Tip: Use the snippet copy button below to quickly install the required packages.
@clerk/astro

npm i @clerk/astro@3.0.18-snapshot.v20260421194054 --save-exact

@clerk/backend

npm i @clerk/backend@3.2.14-snapshot.v20260421194054 --save-exact

@clerk/chrome-extension

npm i @clerk/chrome-extension@3.1.15-snapshot.v20260421194054 --save-exact

@clerk/clerk-js

npm i @clerk/clerk-js@6.7.5-snapshot.v20260421194054 --save-exact

@clerk/dev-cli

npm i @clerk/dev-cli@0.1.1-snapshot.v20260421194054 --save-exact

@clerk/expo

npm i @clerk/expo@3.2.2-snapshot.v20260421194054 --save-exact

@clerk/expo-passkeys

npm i @clerk/expo-passkeys@1.0.15-snapshot.v20260421194054 --save-exact

@clerk/express

npm i @clerk/express@2.1.6-snapshot.v20260421194054 --save-exact

@clerk/fastify

npm i @clerk/fastify@3.1.16-snapshot.v20260421194054 --save-exact

@clerk/hono

npm i @clerk/hono@0.1.16-snapshot.v20260421194054 --save-exact

@clerk/localizations

npm i @clerk/localizations@4.5.3-snapshot.v20260421194054 --save-exact

@clerk/msw

npm i @clerk/msw@0.0.15-snapshot.v20260421194054 --save-exact

@clerk/nextjs

npm i @clerk/nextjs@7.2.4-snapshot.v20260421194054 --save-exact

@clerk/nuxt

npm i @clerk/nuxt@2.2.5-snapshot.v20260421194054 --save-exact

@clerk/react

npm i @clerk/react@6.4.3-snapshot.v20260421194054 --save-exact

@clerk/react-router

npm i @clerk/react-router@3.1.4-snapshot.v20260421194054 --save-exact

@clerk/shared

npm i @clerk/shared@4.8.3-snapshot.v20260421194054 --save-exact

@clerk/tanstack-react-start

npm i @clerk/tanstack-react-start@1.1.4-snapshot.v20260421194054 --save-exact

@clerk/testing

npm i @clerk/testing@2.0.18-snapshot.v20260421194054 --save-exact

@clerk/ui

npm i @clerk/ui@1.6.4-snapshot.v20260421194054 --save-exact

@clerk/upgrade

npm i @clerk/upgrade@2.0.3-snapshot.v20260421194054 --save-exact

@clerk/vue

npm i @clerk/vue@2.0.16-snapshot.v20260421194054 --save-exact

@Railly

Copy link
Copy Markdown
Contributor

@Ephem Tested with a real production instance on vercel.app, auth works end-to-end.

One finding: the default middleware matcher excludes .js files, which prevents /__clerk/npm/@clerk/clerk-js@6/dist/clerk.browser.js from reaching the middleware. Had to add "/__clerk/(.*)" to the matcher manually. Since auto-proxy is meant to work without config, I think clerkMiddleware should handle this automatically or the default matcher should always include __clerk.

Test app: https://v0-auto-proxy-test.vercel.app

@EphemEphem left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is looking really good, I only worry slightly about the backend case, made a suggestion.

We still need to figure out the /__clerk/(.*) not being in the matcher by default too. Not sure that's going to affect this PR though, but we should probably figure it out before merging.

Comment threadpackages/shared/src/proxy.ts
) {
// Auto-detect proxy for supported platform deployments (production only)
if (!options.proxyUrl && !options.domain && options.publishableKey?.startsWith('pk_live_')) {
const hostname = clerkRequest.clerkUrl.hostname;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure a comment is enough here (it also doesn't explain why this is currently safe). It's very hard to fully understand the implications of something like this, especially as code changes over time, so I think it doesn't hurt to be defensive.

I wonder if we could straight up reuse getAutoProxyUrlFromEnvironment here (with minor tweaks)? That way, if the backend is deployed to Vercel auto-proxying will work correctly, which should be fine for the use case we are targeting?

If the backend is hosted separately, I think requiring you to configure a proxyUrl for it yourself is fair instead of picking it up from the header?

@Railly

Copy link
Copy Markdown
Contributor

I'm not sure a comment is enough here (it also doesn't explain why this is currently safe). It's very hard to fully understand the implications of something like this, especially as code changes over time, so I think it doesn't hurt to be defensive.
I wonder if we could straight up reuse getAutoProxyUrlFromEnvironment here (with minor tweaks)? That way, if the backend is deployed to Vercel auto-proxying will work correctly, which should be fine for the use case we are targeting?
If the backend is hosted separately, I think requiring you to configure a proxyUrl for it yourself is fair instead of picking it up from the header?

@Ephem addressed in 43be86f, replaced header-based detection with getAutoProxyUrlFromEnvironment in the backend. If deployed outside Vercel, auto-proxy simply doesn't activate and you configure proxyUrl manually.

@EphemEphem left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Ephem addressed in 43be86f, replaced header-based detection with getAutoProxyUrlFromEnvironment in the backend. If deployed outside Vercel, auto-proxy simply doesn't activate and you configure proxyUrl manually.

Awesome, that sidesteps the concern entirely, thanks!

I think this is looking good to go now, great work Bryce on the original implementation and Railly on picking it up!

@nikosdouvlis Do you have any final concerns?

@nikosdouvlisnikosdouvlis left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM :)

@Railly

Copy link
Copy Markdown
Contributor

hey @nikosdouvlis, could you merge this when you get a chance? I’m not the PR author so I don’t have permissions

@nikosdouvlis
nikosdouvlis merged commit 9b57986 into mainApr 28, 2026
177 of 192 checks passed
@nikosdouvlis
nikosdouvlis deleted the brkalow/auto-proxy-vercel-subdomains branch April 28, 2026 21:10
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@brkalow@dominic-clerk@Railly@nikosdouvlis@Ephem
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(*): auto-proxy for eligible hosts - #8035

Merged
nikosdouvlis merged 35 commits into
mainfrom
brkalow/auto-proxy-vercel-subdomains
Apr 28, 2026
Merged

feat(*): auto-proxy for eligible hosts#8035
nikosdouvlis merged 35 commits into
mainfrom
brkalow/auto-proxy-vercel-subdomains

Conversation

@brkalow

@brkalowbrkalow commented Mar 11, 2026

Copy link
Copy Markdown
Member

Description

This PR implements automatic FAPI proxying for Vercel preview deployments. When an app is deployed to a .vercel.app subdomain without explicit proxy or domain configuration, the SDK automatically routes FAPI requests through the app's own domain via the /__clerk proxy path.

The auto-detection is centralized across all SDKs:

  • Shared utilities (@clerk/shared/proxy): New isVercelPreviewDeploy() helper detects .vercel.app hostnames
  • Client-side (@clerk/clerk-js): Auto-detect in proxyUrl getter when window.location.hostname is .vercel.app
  • Server-side (@clerk/backend): Auto-detect in authenticateContext constructor when request hostname is .vercel.app
  • Next.js middleware (@clerk/nextjs): Auto-enable proxy interception for /__clerk/* requests on .vercel.app hostnames

Guard conditions prevent auto-detection when explicit configuration is provided: proxyUrl, domain, or environment variables NEXT_PUBLIC_CLERK_PROXY_URL / NEXT_PUBLIC_CLERK_DOMAIN.

Test Results

  • @clerk/shared: 981/981 tests passed
  • @clerk/backend: 1114/1114 tests passed
  • @clerk/clerk-js: 624/624 tests passed
  • @clerk/nextjs: 362 tests passed (49 pre-existing failures unrelated to this change)

Build verification: All packages build cleanly with no type errors.

Checklist

  • pnpm test runs as expected
  • pnpm build runs as expected
  • Tests added for new functionality

Type of change

  • 🌟 New feature

Summary by CodeRabbit

  • New Features

    • Automatic proxy detection for Vercel-style hosts: when no explicit proxy or domain is provided, the frontend API routing can auto-configure a relative proxy for eligible deployments; explicit proxy or domain settings take precedence.
  • Bug Fixes

    • Improved handling of relative proxy URLs in non-browser/server contexts and script-host resolution fallback.
  • Tests

    • Added comprehensive tests validating auto-detection, precedence rules, environment-based behavior, and various host scenarios.

@vercel

vercelBot commented Mar 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentApr 27, 2026 7:48pm

Request Review

@changeset-bot

changeset-botBot commented Mar 11, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: d0450ed

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 20 packages
NameType
@clerk/sharedPatch
@clerk/backendPatch
@clerk/clerk-jsPatch
@clerk/nextjsPatch
@clerk/astroPatch
@clerk/chrome-extensionPatch
@clerk/expo-passkeysPatch
@clerk/expoPatch
@clerk/expressPatch
@clerk/fastifyPatch
@clerk/honoPatch
@clerk/localizationsPatch
@clerk/mswPatch
@clerk/nuxtPatch
@clerk/react-routerPatch
@clerk/reactPatch
@clerk/tanstack-react-startPatch
@clerk/testingPatch
@clerk/uiPatch
@clerk/vuePatch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitaiBot commented Mar 11, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds exported functions shouldAutoProxy(hostname: string) and getAutoProxyUrlFromEnvironment(...) to the shared package to detect eligible hostnames (e.g., *.vercel.app) and derive a relative proxy path (/__clerk). Integrates this auto-proxy logic into backend AuthenticateContext, clerk-js core, Next.js middleware, and environment-merging utilities so that when neither proxyUrl nor domain is provided, the proxy may be set to the current origin + /__clerk for eligible environments. Adds tests across packages and a changeset entry.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 20.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title 'feat(*): auto-proxy for eligible hosts' accurately describes the main feature being implemented across the codebase - automatic proxy detection for eligible hostnames (specifically .vercel.app subdomains).

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

…omains
Add automatic FAPI proxying detection for Vercel preview deployments across all SDKs. When an app is deployed to a .vercel.app subdomain without explicit proxy/domain configuration, the SDK automatically routes FAPI requests through the app's own domain via /__clerk proxy path.
- Add isVercelPreviewDeploy() helper in @clerk/shared/proxy
- Auto-detect in clerk-js proxyUrl getter for client-side SDK initialization
- Auto-detect in @clerk/backend authenticateContext for server-side auth
- Enable proxy interception in Next.js middleware for /__clerk/* requests on .vercel.app
- Add comprehensive tests for all three layers (shared, backend, clerk-js, nextjs)
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
@pkg-pr-new

pkg-pr-newBot commented Mar 13, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

npm i https://pkg.pr.new/@clerk/astro@8035

@clerk/backend

npm i https://pkg.pr.new/@clerk/backend@8035

@clerk/chrome-extension

npm i https://pkg.pr.new/@clerk/chrome-extension@8035

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@8035

@clerk/dev-cli

npm i https://pkg.pr.new/@clerk/dev-cli@8035

@clerk/expo

npm i https://pkg.pr.new/@clerk/expo@8035

@clerk/expo-passkeys

npm i https://pkg.pr.new/@clerk/expo-passkeys@8035

@clerk/express

npm i https://pkg.pr.new/@clerk/express@8035

@clerk/fastify

npm i https://pkg.pr.new/@clerk/fastify@8035

@clerk/hono

npm i https://pkg.pr.new/@clerk/hono@8035

@clerk/localizations

npm i https://pkg.pr.new/@clerk/localizations@8035

@clerk/nextjs

npm i https://pkg.pr.new/@clerk/nextjs@8035

@clerk/nuxt

npm i https://pkg.pr.new/@clerk/nuxt@8035

@clerk/react

npm i https://pkg.pr.new/@clerk/react@8035

@clerk/react-router

npm i https://pkg.pr.new/@clerk/react-router@8035

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@8035

@clerk/tanstack-react-start

npm i https://pkg.pr.new/@clerk/tanstack-react-start@8035

@clerk/testing

npm i https://pkg.pr.new/@clerk/testing@8035

@clerk/ui

npm i https://pkg.pr.new/@clerk/ui@8035

@clerk/upgrade

npm i https://pkg.pr.new/@clerk/upgrade@8035

@clerk/vue

npm i https://pkg.pr.new/@clerk/vue@8035

commit: d0450ed

@brkalowbrkalow changed the title Auto-proxy for .vercel.app subdomainsfeat(*): auto-proxy for eligible hostsMar 13, 2026

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.changeset/tiny-badgers-smile.md:
- Line 8: Update the changeset description sentence that currently reads "Add
auto-proxy detection for eligible hosts and generalize the internal helper
naming for future providers." to be specific about which hosts are considered
eligible (e.g., Vercel preview deployments) and to clarify the helper change;
for example, change the description to: "Add auto-proxy detection for Vercel
preview deployments (.vercel.app subdomains) and generalize the internal helper
for future hosting providers." Locate and edit the line containing that
description in the changeset (the sentence beginning "Add auto-proxy detection
for eligible hosts...") and replace it with the clearer, user-facing wording.
- Around line 1-6: The changeset incorrectly marks package bumps as 'patch' but
a new public API (shouldAutoProxy) was added; update the entries so any package
that exports the new function (at least '@clerk/shared', and any packages that
re-export it like '@clerk/backend', '@clerk/clerk-js', '@clerk/nextjs' if
applicable) use 'minor' instead of 'patch' in the
.changeset/tiny-badgers-smile.md file; ensure the header lines for those package
entries read 'minor' to reflect the new public API bump while keeping other
metadata unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0cecde51-9659-43c2-8677-37774a033a71

📥 Commits

Reviewing files that changed from the base of the PR and between dbc580e and 2d729d2.

📒 Files selected for processing (1)
  • .changeset/tiny-badgers-smile.md

Comment thread.changeset/tiny-badgers-smile.md Outdated
Comment on lines +1 to +6
---
'@clerk/backend': patch
'@clerk/clerk-js': patch
'@clerk/nextjs': patch
'@clerk/shared': patch
---

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash# Description: Check if shouldAutoProxy is a public export# Search for shouldAutoProxy exports in the shared package
rg -n "export.*shouldAutoProxy" packages/shared/

Repository: clerk/javascript

Length of output: 152


Update version bumps from patch to minor to reflect the new public API export.

The changeset marks all packages for patch bumps, but the PR introduces a new public API function shouldAutoProxy exported from @clerk/shared. Per semantic versioning standards, new public APIs require minor version bumps, not patch (which is reserved for bug fixes). Update the changeset accordingly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.changeset/tiny-badgers-smile.md around lines 1 - 6, The changeset
incorrectly marks package bumps as 'patch' but a new public API
(shouldAutoProxy) was added; update the entries so any package that exports the
new function (at least '@clerk/shared', and any packages that re-export it like
'@clerk/backend', '@clerk/clerk-js', '@clerk/nextjs' if applicable) use 'minor'
instead of 'patch' in the .changeset/tiny-badgers-smile.md file; ensure the
header lines for those package entries read 'minor' to reflect the new public
API bump while keeping other metadata unchanged.

Comment thread.changeset/tiny-badgers-smile.md Outdated
@brkalow

Copy link
Copy Markdown
MemberAuthor

!snapshot

@github-actions

This comment has been minimized.

@brkalow

Copy link
Copy Markdown
MemberAuthor

!snapshot

@github-actions

This comment has been minimized.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/shared/src/proxy.ts`:
- Around line 45-47: The normalizeHostname function can throw when called with a
malformed URL (e.g., VERCEL_PROJECT_PRODUCTION_URL); wrap the URL parsing in a
try-catch inside normalizeHostname (the function shown using new
URL(...).hostname) and return an empty string on error so config resolution
doesn't crash; ensure callers that rely on normalizeHostname (where
VERCEL_PROJECT_PRODUCTION_URL is passed) can handle an empty string result
gracefully.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: 2ad9a329-ea13-4ffe-86f9-f7dec3b163e7

📥 Commits

Reviewing files that changed from the base of the PR and between 2d729d2 and 72d6bdc.

📒 Files selected for processing (5)
  • .changeset/tiny-badgers-smile.md
  • packages/nextjs/src/utils/__tests__/mergeNextClerkPropsWithEnv.test.ts
  • packages/nextjs/src/utils/mergeNextClerkPropsWithEnv.ts
  • packages/shared/src/__tests__/proxy.spec.ts
  • packages/shared/src/proxy.ts

Comment threadpackages/shared/src/proxy.ts Outdated
Comment on lines +45 to +47
function normalizeHostname(hostnameOrUrl: string): string {
if (hostnameOrUrl.startsWith('http://') || hostnameOrUrl.startsWith('https://')) {
return new URL(hostnameOrUrl).hostname;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash# Verify call path and URL constructor throw behavior
rg -n "normalizeHostname|getAutoProxyUrlFromEnvironment|VERCEL_PROJECT_PRODUCTION_URL" \
packages/shared/src/proxy.ts packages/nextjs/src/utils/mergeNextClerkPropsWithEnv.ts
node - <<'NODE'const samples = ['https://', 'https://?x=1', 'https:///foo', 'https://myapp.vercel.app'];for (const s of samples) { try { console.log(`${s} => hostname="${new URL(s).hostname}"`); } catch (e) { console.log(`${s} => THROWS (${e.name})`); }}NODE

Repository: clerk/javascript

Length of output: 853


🏁 Script executed:

#!/bin/bash# Get full function context at line 45-47 and surrounding linesecho"=== normalizeHostname function context (lines 41-55) ==="
sed -n '41,55p' packages/shared/src/proxy.ts | cat -n
echo -e "\n=== getAutoProxyUrlFromEnvironment call context (lines 70-80) ==="
sed -n '70,80p' packages/shared/src/proxy.ts | cat -n
# Search for tests related to proxy.ts or normalizeHostnameecho -e "\n=== Tests for proxy functions ==="
find . -name "*.test.ts" -o -name "*.spec.ts"| xargs grep -l "normalizeHostname\|getAutoProxyUrlFromEnvironment\|proxy"2>/dev/null | head -10

Repository: clerk/javascript

Length of output: 1707


🏁 Script executed:

#!/bin/bash# Check if there are tests covering error cases for these functionsecho"=== Searching for test files related to proxy ==="
fd -e "test.ts" -e "spec.ts"| xargs grep -l "normalizeHostname\|getAutoProxyUrlFromEnvironment"2>/dev/null
# Also check the test directory structureecho -e "\n=== Checking __tests__ and test directory structure ==="
find packages/shared -type d \( -name "__tests__" -o -name "tests" -o -name "test"\)| head -5

Repository: clerk/javascript

Length of output: 410


🏁 Script executed:

#!/bin/bash# Read the existing proxy test file to see current coverageecho"=== Existing proxy tests ==="
cat packages/shared/src/__tests__/proxy.spec.ts

Repository: clerk/javascript

Length of output: 5211


Handle malformed VERCEL_PROJECT_PRODUCTION_URL in hostname normalization to prevent runtime crashes.

At line 76, normalizeHostname() is called with an environment variable without error handling. If VERCEL_PROJECT_PRODUCTION_URL is malformed (e.g., https:// or https://?x=1), new URL() throws a TypeError, crashing config resolution instead of gracefully returning an empty string. Add try-catch to safely handle this:

Proposed fix
function normalizeHostname(hostnameOrUrl: string): string {
if (hostnameOrUrl.startsWith('http://') || hostnameOrUrl.startsWith('https://')) {
- return new URL(hostnameOrUrl).hostname;+ try {+ return new URL(hostnameOrUrl).hostname;+ } catch {+ return '';+ }
}
return hostnameOrUrl.split('/')[0] || '';
}

Also applies to: 76-77

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/shared/src/proxy.ts` around lines 45 - 47, The normalizeHostname
function can throw when called with a malformed URL (e.g.,
VERCEL_PROJECT_PRODUCTION_URL); wrap the URL parsing in a try-catch inside
normalizeHostname (the function shown using new URL(...).hostname) and return an
empty string on error so config resolution doesn't crash; ensure callers that
rely on normalizeHostname (where VERCEL_PROJECT_PRODUCTION_URL is passed) can
handle an empty string result gracefully.

@brkalow

Copy link
Copy Markdown
MemberAuthor

!snapshot

@github-actions

This comment has been minimized.

@Railly

Copy link
Copy Markdown
Contributor

!snapshot

@github-actions

Copy link
Copy Markdown
Contributor

Hey @Railly - the snapshot version command generated the following package versions:

PackageVersion
@clerk/astro3.0.18-snapshot.v20260421194054
@clerk/backend3.2.14-snapshot.v20260421194054
@clerk/chrome-extension3.1.15-snapshot.v20260421194054
@clerk/clerk-js6.7.5-snapshot.v20260421194054
@clerk/dev-cli0.1.1-snapshot.v20260421194054
@clerk/expo3.2.2-snapshot.v20260421194054
@clerk/expo-passkeys1.0.15-snapshot.v20260421194054
@clerk/express2.1.6-snapshot.v20260421194054
@clerk/fastify3.1.16-snapshot.v20260421194054
@clerk/hono0.1.16-snapshot.v20260421194054
@clerk/localizations4.5.3-snapshot.v20260421194054
@clerk/msw0.0.15-snapshot.v20260421194054
@clerk/nextjs7.2.4-snapshot.v20260421194054
@clerk/nuxt2.2.5-snapshot.v20260421194054
@clerk/react6.4.3-snapshot.v20260421194054
@clerk/react-router3.1.4-snapshot.v20260421194054
@clerk/shared4.8.3-snapshot.v20260421194054
@clerk/tanstack-react-start1.1.4-snapshot.v20260421194054
@clerk/testing2.0.18-snapshot.v20260421194054
@clerk/ui1.6.4-snapshot.v20260421194054
@clerk/upgrade2.0.3-snapshot.v20260421194054
@clerk/vue2.0.16-snapshot.v20260421194054

Tip: Use the snippet copy button below to quickly install the required packages.
@clerk/astro

npm i @clerk/astro@3.0.18-snapshot.v20260421194054 --save-exact

@clerk/backend

npm i @clerk/backend@3.2.14-snapshot.v20260421194054 --save-exact

@clerk/chrome-extension

npm i @clerk/chrome-extension@3.1.15-snapshot.v20260421194054 --save-exact

@clerk/clerk-js

npm i @clerk/clerk-js@6.7.5-snapshot.v20260421194054 --save-exact

@clerk/dev-cli

npm i @clerk/dev-cli@0.1.1-snapshot.v20260421194054 --save-exact

@clerk/expo

npm i @clerk/expo@3.2.2-snapshot.v20260421194054 --save-exact

@clerk/expo-passkeys

npm i @clerk/expo-passkeys@1.0.15-snapshot.v20260421194054 --save-exact

@clerk/express

npm i @clerk/express@2.1.6-snapshot.v20260421194054 --save-exact

@clerk/fastify

npm i @clerk/fastify@3.1.16-snapshot.v20260421194054 --save-exact

@clerk/hono

npm i @clerk/hono@0.1.16-snapshot.v20260421194054 --save-exact

@clerk/localizations

npm i @clerk/localizations@4.5.3-snapshot.v20260421194054 --save-exact

@clerk/msw

npm i @clerk/msw@0.0.15-snapshot.v20260421194054 --save-exact

@clerk/nextjs

npm i @clerk/nextjs@7.2.4-snapshot.v20260421194054 --save-exact

@clerk/nuxt

npm i @clerk/nuxt@2.2.5-snapshot.v20260421194054 --save-exact

@clerk/react

npm i @clerk/react@6.4.3-snapshot.v20260421194054 --save-exact

@clerk/react-router

npm i @clerk/react-router@3.1.4-snapshot.v20260421194054 --save-exact

@clerk/shared

npm i @clerk/shared@4.8.3-snapshot.v20260421194054 --save-exact

@clerk/tanstack-react-start

npm i @clerk/tanstack-react-start@1.1.4-snapshot.v20260421194054 --save-exact

@clerk/testing

npm i @clerk/testing@2.0.18-snapshot.v20260421194054 --save-exact

@clerk/ui

npm i @clerk/ui@1.6.4-snapshot.v20260421194054 --save-exact

@clerk/upgrade

npm i @clerk/upgrade@2.0.3-snapshot.v20260421194054 --save-exact

@clerk/vue

npm i @clerk/vue@2.0.16-snapshot.v20260421194054 --save-exact

@Railly

Copy link
Copy Markdown
Contributor

@Ephem Tested with a real production instance on vercel.app, auth works end-to-end.

One finding: the default middleware matcher excludes .js files, which prevents /__clerk/npm/@clerk/clerk-js@6/dist/clerk.browser.js from reaching the middleware. Had to add "/__clerk/(.*)" to the matcher manually. Since auto-proxy is meant to work without config, I think clerkMiddleware should handle this automatically or the default matcher should always include __clerk.

Test app: https://v0-auto-proxy-test.vercel.app

@EphemEphem left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is looking really good, I only worry slightly about the backend case, made a suggestion.

We still need to figure out the /__clerk/(.*) not being in the matcher by default too. Not sure that's going to affect this PR though, but we should probably figure it out before merging.

Comment threadpackages/shared/src/proxy.ts
) {
// Auto-detect proxy for supported platform deployments (production only)
if (!options.proxyUrl && !options.domain && options.publishableKey?.startsWith('pk_live_')) {
const hostname = clerkRequest.clerkUrl.hostname;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure a comment is enough here (it also doesn't explain why this is currently safe). It's very hard to fully understand the implications of something like this, especially as code changes over time, so I think it doesn't hurt to be defensive.

I wonder if we could straight up reuse getAutoProxyUrlFromEnvironment here (with minor tweaks)? That way, if the backend is deployed to Vercel auto-proxying will work correctly, which should be fine for the use case we are targeting?

If the backend is hosted separately, I think requiring you to configure a proxyUrl for it yourself is fair instead of picking it up from the header?

@Railly

Copy link
Copy Markdown
Contributor

I'm not sure a comment is enough here (it also doesn't explain why this is currently safe). It's very hard to fully understand the implications of something like this, especially as code changes over time, so I think it doesn't hurt to be defensive.
I wonder if we could straight up reuse getAutoProxyUrlFromEnvironment here (with minor tweaks)? That way, if the backend is deployed to Vercel auto-proxying will work correctly, which should be fine for the use case we are targeting?
If the backend is hosted separately, I think requiring you to configure a proxyUrl for it yourself is fair instead of picking it up from the header?

@Ephem addressed in 43be86f, replaced header-based detection with getAutoProxyUrlFromEnvironment in the backend. If deployed outside Vercel, auto-proxy simply doesn't activate and you configure proxyUrl manually.

@EphemEphem left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Ephem addressed in 43be86f, replaced header-based detection with getAutoProxyUrlFromEnvironment in the backend. If deployed outside Vercel, auto-proxy simply doesn't activate and you configure proxyUrl manually.

Awesome, that sidesteps the concern entirely, thanks!

I think this is looking good to go now, great work Bryce on the original implementation and Railly on picking it up!

@nikosdouvlis Do you have any final concerns?

@nikosdouvlisnikosdouvlis left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM :)

@Railly

Copy link
Copy Markdown
Contributor

hey @nikosdouvlis, could you merge this when you get a chance? I’m not the PR author so I don’t have permissions

@nikosdouvlis
nikosdouvlis merged commit 9b57986 into mainApr 28, 2026
177 of 192 checks passed
@nikosdouvlis
nikosdouvlis deleted the brkalow/auto-proxy-vercel-subdomains branch April 28, 2026 21:10
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@brkalow@dominic-clerk@Railly@nikosdouvlis@Ephem
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

feat(*): auto-proxy for eligible hosts - #8035

Merged
nikosdouvlis merged 35 commits into
mainfrom
brkalow/auto-proxy-vercel-subdomains
Apr 28, 2026
Merged

feat(*): auto-proxy for eligible hosts#8035
nikosdouvlis merged 35 commits into
mainfrom
brkalow/auto-proxy-vercel-subdomains

Conversation

@brkalow

@brkalowbrkalow commented Mar 11, 2026

Copy link
Copy Markdown
Member

Description

This PR implements automatic FAPI proxying for Vercel preview deployments. When an app is deployed to a .vercel.app subdomain without explicit proxy or domain configuration, the SDK automatically routes FAPI requests through the app's own domain via the /__clerk proxy path.

The auto-detection is centralized across all SDKs:

  • Shared utilities (@clerk/shared/proxy): New isVercelPreviewDeploy() helper detects .vercel.app hostnames
  • Client-side (@clerk/clerk-js): Auto-detect in proxyUrl getter when window.location.hostname is .vercel.app
  • Server-side (@clerk/backend): Auto-detect in authenticateContext constructor when request hostname is .vercel.app
  • Next.js middleware (@clerk/nextjs): Auto-enable proxy interception for /__clerk/* requests on .vercel.app hostnames

Guard conditions prevent auto-detection when explicit configuration is provided: proxyUrl, domain, or environment variables NEXT_PUBLIC_CLERK_PROXY_URL / NEXT_PUBLIC_CLERK_DOMAIN.

Test Results

  • @clerk/shared: 981/981 tests passed
  • @clerk/backend: 1114/1114 tests passed
  • @clerk/clerk-js: 624/624 tests passed
  • @clerk/nextjs: 362 tests passed (49 pre-existing failures unrelated to this change)

Build verification: All packages build cleanly with no type errors.

Checklist

  • pnpm test runs as expected
  • pnpm build runs as expected
  • Tests added for new functionality

Type of change

  • 🌟 New feature

Summary by CodeRabbit

  • New Features

    • Automatic proxy detection for Vercel-style hosts: when no explicit proxy or domain is provided, the frontend API routing can auto-configure a relative proxy for eligible deployments; explicit proxy or domain settings take precedence.
  • Bug Fixes

    • Improved handling of relative proxy URLs in non-browser/server contexts and script-host resolution fallback.
  • Tests

    • Added comprehensive tests validating auto-detection, precedence rules, environment-based behavior, and various host scenarios.

@vercel

vercelBot commented Mar 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentApr 27, 2026 7:48pm

Request Review

@changeset-bot

changeset-botBot commented Mar 11, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: d0450ed

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 20 packages
NameType
@clerk/sharedPatch
@clerk/backendPatch
@clerk/clerk-jsPatch
@clerk/nextjsPatch
@clerk/astroPatch
@clerk/chrome-extensionPatch
@clerk/expo-passkeysPatch
@clerk/expoPatch
@clerk/expressPatch
@clerk/fastifyPatch
@clerk/honoPatch
@clerk/localizationsPatch
@clerk/mswPatch
@clerk/nuxtPatch
@clerk/react-routerPatch
@clerk/reactPatch
@clerk/tanstack-react-startPatch
@clerk/testingPatch
@clerk/uiPatch
@clerk/vuePatch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitaiBot commented Mar 11, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds exported functions shouldAutoProxy(hostname: string) and getAutoProxyUrlFromEnvironment(...) to the shared package to detect eligible hostnames (e.g., *.vercel.app) and derive a relative proxy path (/__clerk). Integrates this auto-proxy logic into backend AuthenticateContext, clerk-js core, Next.js middleware, and environment-merging utilities so that when neither proxyUrl nor domain is provided, the proxy may be set to the current origin + /__clerk for eligible environments. Adds tests across packages and a changeset entry.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 20.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title 'feat(*): auto-proxy for eligible hosts' accurately describes the main feature being implemented across the codebase - automatic proxy detection for eligible hostnames (specifically .vercel.app subdomains).

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

…omains
Add automatic FAPI proxying detection for Vercel preview deployments across all SDKs. When an app is deployed to a .vercel.app subdomain without explicit proxy/domain configuration, the SDK automatically routes FAPI requests through the app's own domain via /__clerk proxy path.
- Add isVercelPreviewDeploy() helper in @clerk/shared/proxy
- Auto-detect in clerk-js proxyUrl getter for client-side SDK initialization
- Auto-detect in @clerk/backend authenticateContext for server-side auth
- Enable proxy interception in Next.js middleware for /__clerk/* requests on .vercel.app
- Add comprehensive tests for all three layers (shared, backend, clerk-js, nextjs)
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
@pkg-pr-new

pkg-pr-newBot commented Mar 13, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

npm i https://pkg.pr.new/@clerk/astro@8035

@clerk/backend

npm i https://pkg.pr.new/@clerk/backend@8035

@clerk/chrome-extension

npm i https://pkg.pr.new/@clerk/chrome-extension@8035

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@8035

@clerk/dev-cli

npm i https://pkg.pr.new/@clerk/dev-cli@8035

@clerk/expo

npm i https://pkg.pr.new/@clerk/expo@8035

@clerk/expo-passkeys

npm i https://pkg.pr.new/@clerk/expo-passkeys@8035

@clerk/express

npm i https://pkg.pr.new/@clerk/express@8035

@clerk/fastify

npm i https://pkg.pr.new/@clerk/fastify@8035

@clerk/hono

npm i https://pkg.pr.new/@clerk/hono@8035

@clerk/localizations

npm i https://pkg.pr.new/@clerk/localizations@8035

@clerk/nextjs

npm i https://pkg.pr.new/@clerk/nextjs@8035

@clerk/nuxt

npm i https://pkg.pr.new/@clerk/nuxt@8035

@clerk/react

npm i https://pkg.pr.new/@clerk/react@8035

@clerk/react-router

npm i https://pkg.pr.new/@clerk/react-router@8035

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@8035

@clerk/tanstack-react-start

npm i https://pkg.pr.new/@clerk/tanstack-react-start@8035

@clerk/testing

npm i https://pkg.pr.new/@clerk/testing@8035

@clerk/ui

npm i https://pkg.pr.new/@clerk/ui@8035

@clerk/upgrade

npm i https://pkg.pr.new/@clerk/upgrade@8035

@clerk/vue

npm i https://pkg.pr.new/@clerk/vue@8035

commit: d0450ed

@brkalowbrkalow changed the title Auto-proxy for .vercel.app subdomainsfeat(*): auto-proxy for eligible hostsMar 13, 2026

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.changeset/tiny-badgers-smile.md:
- Line 8: Update the changeset description sentence that currently reads "Add
auto-proxy detection for eligible hosts and generalize the internal helper
naming for future providers." to be specific about which hosts are considered
eligible (e.g., Vercel preview deployments) and to clarify the helper change;
for example, change the description to: "Add auto-proxy detection for Vercel
preview deployments (.vercel.app subdomains) and generalize the internal helper
for future hosting providers." Locate and edit the line containing that
description in the changeset (the sentence beginning "Add auto-proxy detection
for eligible hosts...") and replace it with the clearer, user-facing wording.
- Around line 1-6: The changeset incorrectly marks package bumps as 'patch' but
a new public API (shouldAutoProxy) was added; update the entries so any package
that exports the new function (at least '@clerk/shared', and any packages that
re-export it like '@clerk/backend', '@clerk/clerk-js', '@clerk/nextjs' if
applicable) use 'minor' instead of 'patch' in the
.changeset/tiny-badgers-smile.md file; ensure the header lines for those package
entries read 'minor' to reflect the new public API bump while keeping other
metadata unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0cecde51-9659-43c2-8677-37774a033a71

📥 Commits

Reviewing files that changed from the base of the PR and between dbc580e and 2d729d2.

📒 Files selected for processing (1)
  • .changeset/tiny-badgers-smile.md

Comment thread.changeset/tiny-badgers-smile.md Outdated
Comment on lines +1 to +6
---
'@clerk/backend': patch
'@clerk/clerk-js': patch
'@clerk/nextjs': patch
'@clerk/shared': patch
---

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash# Description: Check if shouldAutoProxy is a public export# Search for shouldAutoProxy exports in the shared package
rg -n "export.*shouldAutoProxy" packages/shared/

Repository: clerk/javascript

Length of output: 152


Update version bumps from patch to minor to reflect the new public API export.

The changeset marks all packages for patch bumps, but the PR introduces a new public API function shouldAutoProxy exported from @clerk/shared. Per semantic versioning standards, new public APIs require minor version bumps, not patch (which is reserved for bug fixes). Update the changeset accordingly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.changeset/tiny-badgers-smile.md around lines 1 - 6, The changeset
incorrectly marks package bumps as 'patch' but a new public API
(shouldAutoProxy) was added; update the entries so any package that exports the
new function (at least '@clerk/shared', and any packages that re-export it like
'@clerk/backend', '@clerk/clerk-js', '@clerk/nextjs' if applicable) use 'minor'
instead of 'patch' in the .changeset/tiny-badgers-smile.md file; ensure the
header lines for those package entries read 'minor' to reflect the new public
API bump while keeping other metadata unchanged.

Comment thread.changeset/tiny-badgers-smile.md Outdated
@brkalow

Copy link
Copy Markdown
MemberAuthor

!snapshot

@github-actions

This comment has been minimized.

@brkalow

Copy link
Copy Markdown
MemberAuthor

!snapshot

@github-actions

This comment has been minimized.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/shared/src/proxy.ts`:
- Around line 45-47: The normalizeHostname function can throw when called with a
malformed URL (e.g., VERCEL_PROJECT_PRODUCTION_URL); wrap the URL parsing in a
try-catch inside normalizeHostname (the function shown using new
URL(...).hostname) and return an empty string on error so config resolution
doesn't crash; ensure callers that rely on normalizeHostname (where
VERCEL_PROJECT_PRODUCTION_URL is passed) can handle an empty string result
gracefully.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: 2ad9a329-ea13-4ffe-86f9-f7dec3b163e7

📥 Commits

Reviewing files that changed from the base of the PR and between 2d729d2 and 72d6bdc.

📒 Files selected for processing (5)
  • .changeset/tiny-badgers-smile.md
  • packages/nextjs/src/utils/__tests__/mergeNextClerkPropsWithEnv.test.ts
  • packages/nextjs/src/utils/mergeNextClerkPropsWithEnv.ts
  • packages/shared/src/__tests__/proxy.spec.ts
  • packages/shared/src/proxy.ts

Comment threadpackages/shared/src/proxy.ts Outdated
Comment on lines +45 to +47
function normalizeHostname(hostnameOrUrl: string): string {
if (hostnameOrUrl.startsWith('http://') || hostnameOrUrl.startsWith('https://')) {
return new URL(hostnameOrUrl).hostname;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash# Verify call path and URL constructor throw behavior
rg -n "normalizeHostname|getAutoProxyUrlFromEnvironment|VERCEL_PROJECT_PRODUCTION_URL" \
packages/shared/src/proxy.ts packages/nextjs/src/utils/mergeNextClerkPropsWithEnv.ts
node - <<'NODE'const samples = ['https://', 'https://?x=1', 'https:///foo', 'https://myapp.vercel.app'];for (const s of samples) { try { console.log(`${s} => hostname="${new URL(s).hostname}"`); } catch (e) { console.log(`${s} => THROWS (${e.name})`); }}NODE

Repository: clerk/javascript

Length of output: 853


🏁 Script executed:

#!/bin/bash# Get full function context at line 45-47 and surrounding linesecho"=== normalizeHostname function context (lines 41-55) ==="
sed -n '41,55p' packages/shared/src/proxy.ts | cat -n
echo -e "\n=== getAutoProxyUrlFromEnvironment call context (lines 70-80) ==="
sed -n '70,80p' packages/shared/src/proxy.ts | cat -n
# Search for tests related to proxy.ts or normalizeHostnameecho -e "\n=== Tests for proxy functions ==="
find . -name "*.test.ts" -o -name "*.spec.ts"| xargs grep -l "normalizeHostname\|getAutoProxyUrlFromEnvironment\|proxy"2>/dev/null | head -10

Repository: clerk/javascript

Length of output: 1707


🏁 Script executed:

#!/bin/bash# Check if there are tests covering error cases for these functionsecho"=== Searching for test files related to proxy ==="
fd -e "test.ts" -e "spec.ts"| xargs grep -l "normalizeHostname\|getAutoProxyUrlFromEnvironment"2>/dev/null
# Also check the test directory structureecho -e "\n=== Checking __tests__ and test directory structure ==="
find packages/shared -type d \( -name "__tests__" -o -name "tests" -o -name "test"\)| head -5

Repository: clerk/javascript

Length of output: 410


🏁 Script executed:

#!/bin/bash# Read the existing proxy test file to see current coverageecho"=== Existing proxy tests ==="
cat packages/shared/src/__tests__/proxy.spec.ts

Repository: clerk/javascript

Length of output: 5211


Handle malformed VERCEL_PROJECT_PRODUCTION_URL in hostname normalization to prevent runtime crashes.

At line 76, normalizeHostname() is called with an environment variable without error handling. If VERCEL_PROJECT_PRODUCTION_URL is malformed (e.g., https:// or https://?x=1), new URL() throws a TypeError, crashing config resolution instead of gracefully returning an empty string. Add try-catch to safely handle this:

Proposed fix
function normalizeHostname(hostnameOrUrl: string): string {
if (hostnameOrUrl.startsWith('http://') || hostnameOrUrl.startsWith('https://')) {
- return new URL(hostnameOrUrl).hostname;+ try {+ return new URL(hostnameOrUrl).hostname;+ } catch {+ return '';+ }
}
return hostnameOrUrl.split('/')[0] || '';
}

Also applies to: 76-77

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/shared/src/proxy.ts` around lines 45 - 47, The normalizeHostname
function can throw when called with a malformed URL (e.g.,
VERCEL_PROJECT_PRODUCTION_URL); wrap the URL parsing in a try-catch inside
normalizeHostname (the function shown using new URL(...).hostname) and return an
empty string on error so config resolution doesn't crash; ensure callers that
rely on normalizeHostname (where VERCEL_PROJECT_PRODUCTION_URL is passed) can
handle an empty string result gracefully.

@brkalow

Copy link
Copy Markdown
MemberAuthor

!snapshot

@github-actions

This comment has been minimized.

@Railly

Copy link
Copy Markdown
Contributor

!snapshot

@github-actions

Copy link
Copy Markdown
Contributor

Hey @Railly - the snapshot version command generated the following package versions:

PackageVersion
@clerk/astro3.0.18-snapshot.v20260421194054
@clerk/backend3.2.14-snapshot.v20260421194054
@clerk/chrome-extension3.1.15-snapshot.v20260421194054
@clerk/clerk-js6.7.5-snapshot.v20260421194054
@clerk/dev-cli0.1.1-snapshot.v20260421194054
@clerk/expo3.2.2-snapshot.v20260421194054
@clerk/expo-passkeys1.0.15-snapshot.v20260421194054
@clerk/express2.1.6-snapshot.v20260421194054
@clerk/fastify3.1.16-snapshot.v20260421194054
@clerk/hono0.1.16-snapshot.v20260421194054
@clerk/localizations4.5.3-snapshot.v20260421194054
@clerk/msw0.0.15-snapshot.v20260421194054
@clerk/nextjs7.2.4-snapshot.v20260421194054
@clerk/nuxt2.2.5-snapshot.v20260421194054
@clerk/react6.4.3-snapshot.v20260421194054
@clerk/react-router3.1.4-snapshot.v20260421194054
@clerk/shared4.8.3-snapshot.v20260421194054
@clerk/tanstack-react-start1.1.4-snapshot.v20260421194054
@clerk/testing2.0.18-snapshot.v20260421194054
@clerk/ui1.6.4-snapshot.v20260421194054
@clerk/upgrade2.0.3-snapshot.v20260421194054
@clerk/vue2.0.16-snapshot.v20260421194054

Tip: Use the snippet copy button below to quickly install the required packages.
@clerk/astro

npm i @clerk/astro@3.0.18-snapshot.v20260421194054 --save-exact

@clerk/backend

npm i @clerk/backend@3.2.14-snapshot.v20260421194054 --save-exact

@clerk/chrome-extension

npm i @clerk/chrome-extension@3.1.15-snapshot.v20260421194054 --save-exact

@clerk/clerk-js

npm i @clerk/clerk-js@6.7.5-snapshot.v20260421194054 --save-exact

@clerk/dev-cli

npm i @clerk/dev-cli@0.1.1-snapshot.v20260421194054 --save-exact

@clerk/expo

npm i @clerk/expo@3.2.2-snapshot.v20260421194054 --save-exact

@clerk/expo-passkeys

npm i @clerk/expo-passkeys@1.0.15-snapshot.v20260421194054 --save-exact

@clerk/express

npm i @clerk/express@2.1.6-snapshot.v20260421194054 --save-exact

@clerk/fastify

npm i @clerk/fastify@3.1.16-snapshot.v20260421194054 --save-exact

@clerk/hono

npm i @clerk/hono@0.1.16-snapshot.v20260421194054 --save-exact

@clerk/localizations

npm i @clerk/localizations@4.5.3-snapshot.v20260421194054 --save-exact

@clerk/msw

npm i @clerk/msw@0.0.15-snapshot.v20260421194054 --save-exact

@clerk/nextjs

npm i @clerk/nextjs@7.2.4-snapshot.v20260421194054 --save-exact

@clerk/nuxt

npm i @clerk/nuxt@2.2.5-snapshot.v20260421194054 --save-exact

@clerk/react

npm i @clerk/react@6.4.3-snapshot.v20260421194054 --save-exact

@clerk/react-router

npm i @clerk/react-router@3.1.4-snapshot.v20260421194054 --save-exact

@clerk/shared

npm i @clerk/shared@4.8.3-snapshot.v20260421194054 --save-exact

@clerk/tanstack-react-start

npm i @clerk/tanstack-react-start@1.1.4-snapshot.v20260421194054 --save-exact

@clerk/testing

npm i @clerk/testing@2.0.18-snapshot.v20260421194054 --save-exact

@clerk/ui

npm i @clerk/ui@1.6.4-snapshot.v20260421194054 --save-exact

@clerk/upgrade

npm i @clerk/upgrade@2.0.3-snapshot.v20260421194054 --save-exact

@clerk/vue

npm i @clerk/vue@2.0.16-snapshot.v20260421194054 --save-exact

@Railly

Copy link
Copy Markdown
Contributor

@Ephem Tested with a real production instance on vercel.app, auth works end-to-end.

One finding: the default middleware matcher excludes .js files, which prevents /__clerk/npm/@clerk/clerk-js@6/dist/clerk.browser.js from reaching the middleware. Had to add "/__clerk/(.*)" to the matcher manually. Since auto-proxy is meant to work without config, I think clerkMiddleware should handle this automatically or the default matcher should always include __clerk.

Test app: https://v0-auto-proxy-test.vercel.app

@EphemEphem left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is looking really good, I only worry slightly about the backend case, made a suggestion.

We still need to figure out the /__clerk/(.*) not being in the matcher by default too. Not sure that's going to affect this PR though, but we should probably figure it out before merging.

Comment threadpackages/shared/src/proxy.ts
) {
// Auto-detect proxy for supported platform deployments (production only)
if (!options.proxyUrl && !options.domain && options.publishableKey?.startsWith('pk_live_')) {
const hostname = clerkRequest.clerkUrl.hostname;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure a comment is enough here (it also doesn't explain why this is currently safe). It's very hard to fully understand the implications of something like this, especially as code changes over time, so I think it doesn't hurt to be defensive.

I wonder if we could straight up reuse getAutoProxyUrlFromEnvironment here (with minor tweaks)? That way, if the backend is deployed to Vercel auto-proxying will work correctly, which should be fine for the use case we are targeting?

If the backend is hosted separately, I think requiring you to configure a proxyUrl for it yourself is fair instead of picking it up from the header?

@Railly

Copy link
Copy Markdown
Contributor

I'm not sure a comment is enough here (it also doesn't explain why this is currently safe). It's very hard to fully understand the implications of something like this, especially as code changes over time, so I think it doesn't hurt to be defensive.
I wonder if we could straight up reuse getAutoProxyUrlFromEnvironment here (with minor tweaks)? That way, if the backend is deployed to Vercel auto-proxying will work correctly, which should be fine for the use case we are targeting?
If the backend is hosted separately, I think requiring you to configure a proxyUrl for it yourself is fair instead of picking it up from the header?

@Ephem addressed in 43be86f, replaced header-based detection with getAutoProxyUrlFromEnvironment in the backend. If deployed outside Vercel, auto-proxy simply doesn't activate and you configure proxyUrl manually.

@EphemEphem left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Ephem addressed in 43be86f, replaced header-based detection with getAutoProxyUrlFromEnvironment in the backend. If deployed outside Vercel, auto-proxy simply doesn't activate and you configure proxyUrl manually.

Awesome, that sidesteps the concern entirely, thanks!

I think this is looking good to go now, great work Bryce on the original implementation and Railly on picking it up!

@nikosdouvlis Do you have any final concerns?

@nikosdouvlisnikosdouvlis left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM :)

@Railly

Copy link
Copy Markdown
Contributor

hey @nikosdouvlis, could you merge this when you get a chance? I’m not the PR author so I don’t have permissions

@nikosdouvlis
nikosdouvlis merged commit 9b57986 into mainApr 28, 2026
177 of 192 checks passed
@nikosdouvlis
nikosdouvlis deleted the brkalow/auto-proxy-vercel-subdomains branch April 28, 2026 21:10
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@brkalow@dominic-clerk@Railly@nikosdouvlis@Ephem
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(*): auto-proxy for eligible hosts - #8035

Merged
nikosdouvlis merged 35 commits into
mainfrom
brkalow/auto-proxy-vercel-subdomains
Apr 28, 2026
Merged

feat(*): auto-proxy for eligible hosts#8035
nikosdouvlis merged 35 commits into
mainfrom
brkalow/auto-proxy-vercel-subdomains

Conversation

@brkalow

@brkalowbrkalow commented Mar 11, 2026

Copy link
Copy Markdown
Member

Description

This PR implements automatic FAPI proxying for Vercel preview deployments. When an app is deployed to a .vercel.app subdomain without explicit proxy or domain configuration, the SDK automatically routes FAPI requests through the app's own domain via the /__clerk proxy path.

The auto-detection is centralized across all SDKs:

  • Shared utilities (@clerk/shared/proxy): New isVercelPreviewDeploy() helper detects .vercel.app hostnames
  • Client-side (@clerk/clerk-js): Auto-detect in proxyUrl getter when window.location.hostname is .vercel.app
  • Server-side (@clerk/backend): Auto-detect in authenticateContext constructor when request hostname is .vercel.app
  • Next.js middleware (@clerk/nextjs): Auto-enable proxy interception for /__clerk/* requests on .vercel.app hostnames

Guard conditions prevent auto-detection when explicit configuration is provided: proxyUrl, domain, or environment variables NEXT_PUBLIC_CLERK_PROXY_URL / NEXT_PUBLIC_CLERK_DOMAIN.

Test Results

  • @clerk/shared: 981/981 tests passed
  • @clerk/backend: 1114/1114 tests passed
  • @clerk/clerk-js: 624/624 tests passed
  • @clerk/nextjs: 362 tests passed (49 pre-existing failures unrelated to this change)

Build verification: All packages build cleanly with no type errors.

Checklist

  • pnpm test runs as expected
  • pnpm build runs as expected
  • Tests added for new functionality

Type of change

  • 🌟 New feature

Summary by CodeRabbit

  • New Features

    • Automatic proxy detection for Vercel-style hosts: when no explicit proxy or domain is provided, the frontend API routing can auto-configure a relative proxy for eligible deployments; explicit proxy or domain settings take precedence.
  • Bug Fixes

    • Improved handling of relative proxy URLs in non-browser/server contexts and script-host resolution fallback.
  • Tests

    • Added comprehensive tests validating auto-detection, precedence rules, environment-based behavior, and various host scenarios.

@vercel

vercelBot commented Mar 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentApr 27, 2026 7:48pm

Request Review

@changeset-bot

changeset-botBot commented Mar 11, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: d0450ed

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 20 packages
NameType
@clerk/sharedPatch
@clerk/backendPatch
@clerk/clerk-jsPatch
@clerk/nextjsPatch
@clerk/astroPatch
@clerk/chrome-extensionPatch
@clerk/expo-passkeysPatch
@clerk/expoPatch
@clerk/expressPatch
@clerk/fastifyPatch
@clerk/honoPatch
@clerk/localizationsPatch
@clerk/mswPatch
@clerk/nuxtPatch
@clerk/react-routerPatch
@clerk/reactPatch
@clerk/tanstack-react-startPatch
@clerk/testingPatch
@clerk/uiPatch
@clerk/vuePatch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitaiBot commented Mar 11, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds exported functions shouldAutoProxy(hostname: string) and getAutoProxyUrlFromEnvironment(...) to the shared package to detect eligible hostnames (e.g., *.vercel.app) and derive a relative proxy path (/__clerk). Integrates this auto-proxy logic into backend AuthenticateContext, clerk-js core, Next.js middleware, and environment-merging utilities so that when neither proxyUrl nor domain is provided, the proxy may be set to the current origin + /__clerk for eligible environments. Adds tests across packages and a changeset entry.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 20.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title 'feat(*): auto-proxy for eligible hosts' accurately describes the main feature being implemented across the codebase - automatic proxy detection for eligible hostnames (specifically .vercel.app subdomains).

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

…omains
Add automatic FAPI proxying detection for Vercel preview deployments across all SDKs. When an app is deployed to a .vercel.app subdomain without explicit proxy/domain configuration, the SDK automatically routes FAPI requests through the app's own domain via /__clerk proxy path.
- Add isVercelPreviewDeploy() helper in @clerk/shared/proxy
- Auto-detect in clerk-js proxyUrl getter for client-side SDK initialization
- Auto-detect in @clerk/backend authenticateContext for server-side auth
- Enable proxy interception in Next.js middleware for /__clerk/* requests on .vercel.app
- Add comprehensive tests for all three layers (shared, backend, clerk-js, nextjs)
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
@pkg-pr-new

pkg-pr-newBot commented Mar 13, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

npm i https://pkg.pr.new/@clerk/astro@8035

@clerk/backend

npm i https://pkg.pr.new/@clerk/backend@8035

@clerk/chrome-extension

npm i https://pkg.pr.new/@clerk/chrome-extension@8035

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@8035

@clerk/dev-cli

npm i https://pkg.pr.new/@clerk/dev-cli@8035

@clerk/expo

npm i https://pkg.pr.new/@clerk/expo@8035

@clerk/expo-passkeys

npm i https://pkg.pr.new/@clerk/expo-passkeys@8035

@clerk/express

npm i https://pkg.pr.new/@clerk/express@8035

@clerk/fastify

npm i https://pkg.pr.new/@clerk/fastify@8035

@clerk/hono

npm i https://pkg.pr.new/@clerk/hono@8035

@clerk/localizations

npm i https://pkg.pr.new/@clerk/localizations@8035

@clerk/nextjs

npm i https://pkg.pr.new/@clerk/nextjs@8035

@clerk/nuxt

npm i https://pkg.pr.new/@clerk/nuxt@8035

@clerk/react

npm i https://pkg.pr.new/@clerk/react@8035

@clerk/react-router

npm i https://pkg.pr.new/@clerk/react-router@8035

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@8035

@clerk/tanstack-react-start

npm i https://pkg.pr.new/@clerk/tanstack-react-start@8035

@clerk/testing

npm i https://pkg.pr.new/@clerk/testing@8035

@clerk/ui

npm i https://pkg.pr.new/@clerk/ui@8035

@clerk/upgrade

npm i https://pkg.pr.new/@clerk/upgrade@8035

@clerk/vue

npm i https://pkg.pr.new/@clerk/vue@8035

commit: d0450ed

@brkalowbrkalow changed the title Auto-proxy for .vercel.app subdomainsfeat(*): auto-proxy for eligible hostsMar 13, 2026

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.changeset/tiny-badgers-smile.md:
- Line 8: Update the changeset description sentence that currently reads "Add
auto-proxy detection for eligible hosts and generalize the internal helper
naming for future providers." to be specific about which hosts are considered
eligible (e.g., Vercel preview deployments) and to clarify the helper change;
for example, change the description to: "Add auto-proxy detection for Vercel
preview deployments (.vercel.app subdomains) and generalize the internal helper
for future hosting providers." Locate and edit the line containing that
description in the changeset (the sentence beginning "Add auto-proxy detection
for eligible hosts...") and replace it with the clearer, user-facing wording.
- Around line 1-6: The changeset incorrectly marks package bumps as 'patch' but
a new public API (shouldAutoProxy) was added; update the entries so any package
that exports the new function (at least '@clerk/shared', and any packages that
re-export it like '@clerk/backend', '@clerk/clerk-js', '@clerk/nextjs' if
applicable) use 'minor' instead of 'patch' in the
.changeset/tiny-badgers-smile.md file; ensure the header lines for those package
entries read 'minor' to reflect the new public API bump while keeping other
metadata unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0cecde51-9659-43c2-8677-37774a033a71

📥 Commits

Reviewing files that changed from the base of the PR and between dbc580e and 2d729d2.

📒 Files selected for processing (1)
  • .changeset/tiny-badgers-smile.md

Comment thread.changeset/tiny-badgers-smile.md Outdated
Comment on lines +1 to +6
---
'@clerk/backend': patch
'@clerk/clerk-js': patch
'@clerk/nextjs': patch
'@clerk/shared': patch
---

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash# Description: Check if shouldAutoProxy is a public export# Search for shouldAutoProxy exports in the shared package
rg -n "export.*shouldAutoProxy" packages/shared/

Repository: clerk/javascript

Length of output: 152


Update version bumps from patch to minor to reflect the new public API export.

The changeset marks all packages for patch bumps, but the PR introduces a new public API function shouldAutoProxy exported from @clerk/shared. Per semantic versioning standards, new public APIs require minor version bumps, not patch (which is reserved for bug fixes). Update the changeset accordingly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.changeset/tiny-badgers-smile.md around lines 1 - 6, The changeset
incorrectly marks package bumps as 'patch' but a new public API
(shouldAutoProxy) was added; update the entries so any package that exports the
new function (at least '@clerk/shared', and any packages that re-export it like
'@clerk/backend', '@clerk/clerk-js', '@clerk/nextjs' if applicable) use 'minor'
instead of 'patch' in the .changeset/tiny-badgers-smile.md file; ensure the
header lines for those package entries read 'minor' to reflect the new public
API bump while keeping other metadata unchanged.

Comment thread.changeset/tiny-badgers-smile.md Outdated
@brkalow

Copy link
Copy Markdown
MemberAuthor

!snapshot

@github-actions

This comment has been minimized.

@brkalow

Copy link
Copy Markdown
MemberAuthor

!snapshot

@github-actions

This comment has been minimized.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/shared/src/proxy.ts`:
- Around line 45-47: The normalizeHostname function can throw when called with a
malformed URL (e.g., VERCEL_PROJECT_PRODUCTION_URL); wrap the URL parsing in a
try-catch inside normalizeHostname (the function shown using new
URL(...).hostname) and return an empty string on error so config resolution
doesn't crash; ensure callers that rely on normalizeHostname (where
VERCEL_PROJECT_PRODUCTION_URL is passed) can handle an empty string result
gracefully.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: 2ad9a329-ea13-4ffe-86f9-f7dec3b163e7

📥 Commits

Reviewing files that changed from the base of the PR and between 2d729d2 and 72d6bdc.

📒 Files selected for processing (5)
  • .changeset/tiny-badgers-smile.md
  • packages/nextjs/src/utils/__tests__/mergeNextClerkPropsWithEnv.test.ts
  • packages/nextjs/src/utils/mergeNextClerkPropsWithEnv.ts
  • packages/shared/src/__tests__/proxy.spec.ts
  • packages/shared/src/proxy.ts

Comment threadpackages/shared/src/proxy.ts Outdated
Comment on lines +45 to +47
function normalizeHostname(hostnameOrUrl: string): string {
if (hostnameOrUrl.startsWith('http://') || hostnameOrUrl.startsWith('https://')) {
return new URL(hostnameOrUrl).hostname;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash# Verify call path and URL constructor throw behavior
rg -n "normalizeHostname|getAutoProxyUrlFromEnvironment|VERCEL_PROJECT_PRODUCTION_URL" \
packages/shared/src/proxy.ts packages/nextjs/src/utils/mergeNextClerkPropsWithEnv.ts
node - <<'NODE'const samples = ['https://', 'https://?x=1', 'https:///foo', 'https://myapp.vercel.app'];for (const s of samples) { try { console.log(`${s} => hostname="${new URL(s).hostname}"`); } catch (e) { console.log(`${s} => THROWS (${e.name})`); }}NODE

Repository: clerk/javascript

Length of output: 853


🏁 Script executed:

#!/bin/bash# Get full function context at line 45-47 and surrounding linesecho"=== normalizeHostname function context (lines 41-55) ==="
sed -n '41,55p' packages/shared/src/proxy.ts | cat -n
echo -e "\n=== getAutoProxyUrlFromEnvironment call context (lines 70-80) ==="
sed -n '70,80p' packages/shared/src/proxy.ts | cat -n
# Search for tests related to proxy.ts or normalizeHostnameecho -e "\n=== Tests for proxy functions ==="
find . -name "*.test.ts" -o -name "*.spec.ts"| xargs grep -l "normalizeHostname\|getAutoProxyUrlFromEnvironment\|proxy"2>/dev/null | head -10

Repository: clerk/javascript

Length of output: 1707


🏁 Script executed:

#!/bin/bash# Check if there are tests covering error cases for these functionsecho"=== Searching for test files related to proxy ==="
fd -e "test.ts" -e "spec.ts"| xargs grep -l "normalizeHostname\|getAutoProxyUrlFromEnvironment"2>/dev/null
# Also check the test directory structureecho -e "\n=== Checking __tests__ and test directory structure ==="
find packages/shared -type d \( -name "__tests__" -o -name "tests" -o -name "test"\)| head -5

Repository: clerk/javascript

Length of output: 410


🏁 Script executed:

#!/bin/bash# Read the existing proxy test file to see current coverageecho"=== Existing proxy tests ==="
cat packages/shared/src/__tests__/proxy.spec.ts

Repository: clerk/javascript

Length of output: 5211


Handle malformed VERCEL_PROJECT_PRODUCTION_URL in hostname normalization to prevent runtime crashes.

At line 76, normalizeHostname() is called with an environment variable without error handling. If VERCEL_PROJECT_PRODUCTION_URL is malformed (e.g., https:// or https://?x=1), new URL() throws a TypeError, crashing config resolution instead of gracefully returning an empty string. Add try-catch to safely handle this:

Proposed fix
function normalizeHostname(hostnameOrUrl: string): string {
if (hostnameOrUrl.startsWith('http://') || hostnameOrUrl.startsWith('https://')) {
- return new URL(hostnameOrUrl).hostname;+ try {+ return new URL(hostnameOrUrl).hostname;+ } catch {+ return '';+ }
}
return hostnameOrUrl.split('/')[0] || '';
}

Also applies to: 76-77

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/shared/src/proxy.ts` around lines 45 - 47, The normalizeHostname
function can throw when called with a malformed URL (e.g.,
VERCEL_PROJECT_PRODUCTION_URL); wrap the URL parsing in a try-catch inside
normalizeHostname (the function shown using new URL(...).hostname) and return an
empty string on error so config resolution doesn't crash; ensure callers that
rely on normalizeHostname (where VERCEL_PROJECT_PRODUCTION_URL is passed) can
handle an empty string result gracefully.

@brkalow

Copy link
Copy Markdown
MemberAuthor

!snapshot

@github-actions

This comment has been minimized.

@Railly

Copy link
Copy Markdown
Contributor

!snapshot

@github-actions

Copy link
Copy Markdown
Contributor

Hey @Railly - the snapshot version command generated the following package versions:

PackageVersion
@clerk/astro3.0.18-snapshot.v20260421194054
@clerk/backend3.2.14-snapshot.v20260421194054
@clerk/chrome-extension3.1.15-snapshot.v20260421194054
@clerk/clerk-js6.7.5-snapshot.v20260421194054
@clerk/dev-cli0.1.1-snapshot.v20260421194054
@clerk/expo3.2.2-snapshot.v20260421194054
@clerk/expo-passkeys1.0.15-snapshot.v20260421194054
@clerk/express2.1.6-snapshot.v20260421194054
@clerk/fastify3.1.16-snapshot.v20260421194054
@clerk/hono0.1.16-snapshot.v20260421194054
@clerk/localizations4.5.3-snapshot.v20260421194054
@clerk/msw0.0.15-snapshot.v20260421194054
@clerk/nextjs7.2.4-snapshot.v20260421194054
@clerk/nuxt2.2.5-snapshot.v20260421194054
@clerk/react6.4.3-snapshot.v20260421194054
@clerk/react-router3.1.4-snapshot.v20260421194054
@clerk/shared4.8.3-snapshot.v20260421194054
@clerk/tanstack-react-start1.1.4-snapshot.v20260421194054
@clerk/testing2.0.18-snapshot.v20260421194054
@clerk/ui1.6.4-snapshot.v20260421194054
@clerk/upgrade2.0.3-snapshot.v20260421194054
@clerk/vue2.0.16-snapshot.v20260421194054

Tip: Use the snippet copy button below to quickly install the required packages.
@clerk/astro

npm i @clerk/astro@3.0.18-snapshot.v20260421194054 --save-exact

@clerk/backend

npm i @clerk/backend@3.2.14-snapshot.v20260421194054 --save-exact

@clerk/chrome-extension

npm i @clerk/chrome-extension@3.1.15-snapshot.v20260421194054 --save-exact

@clerk/clerk-js

npm i @clerk/clerk-js@6.7.5-snapshot.v20260421194054 --save-exact

@clerk/dev-cli

npm i @clerk/dev-cli@0.1.1-snapshot.v20260421194054 --save-exact

@clerk/expo

npm i @clerk/expo@3.2.2-snapshot.v20260421194054 --save-exact

@clerk/expo-passkeys

npm i @clerk/expo-passkeys@1.0.15-snapshot.v20260421194054 --save-exact

@clerk/express

npm i @clerk/express@2.1.6-snapshot.v20260421194054 --save-exact

@clerk/fastify

npm i @clerk/fastify@3.1.16-snapshot.v20260421194054 --save-exact

@clerk/hono

npm i @clerk/hono@0.1.16-snapshot.v20260421194054 --save-exact

@clerk/localizations

npm i @clerk/localizations@4.5.3-snapshot.v20260421194054 --save-exact

@clerk/msw

npm i @clerk/msw@0.0.15-snapshot.v20260421194054 --save-exact

@clerk/nextjs

npm i @clerk/nextjs@7.2.4-snapshot.v20260421194054 --save-exact

@clerk/nuxt

npm i @clerk/nuxt@2.2.5-snapshot.v20260421194054 --save-exact

@clerk/react

npm i @clerk/react@6.4.3-snapshot.v20260421194054 --save-exact

@clerk/react-router

npm i @clerk/react-router@3.1.4-snapshot.v20260421194054 --save-exact

@clerk/shared

npm i @clerk/shared@4.8.3-snapshot.v20260421194054 --save-exact

@clerk/tanstack-react-start

npm i @clerk/tanstack-react-start@1.1.4-snapshot.v20260421194054 --save-exact

@clerk/testing

npm i @clerk/testing@2.0.18-snapshot.v20260421194054 --save-exact

@clerk/ui

npm i @clerk/ui@1.6.4-snapshot.v20260421194054 --save-exact

@clerk/upgrade

npm i @clerk/upgrade@2.0.3-snapshot.v20260421194054 --save-exact

@clerk/vue

npm i @clerk/vue@2.0.16-snapshot.v20260421194054 --save-exact

@Railly

Copy link
Copy Markdown
Contributor

@Ephem Tested with a real production instance on vercel.app, auth works end-to-end.

One finding: the default middleware matcher excludes .js files, which prevents /__clerk/npm/@clerk/clerk-js@6/dist/clerk.browser.js from reaching the middleware. Had to add "/__clerk/(.*)" to the matcher manually. Since auto-proxy is meant to work without config, I think clerkMiddleware should handle this automatically or the default matcher should always include __clerk.

Test app: https://v0-auto-proxy-test.vercel.app

@EphemEphem left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is looking really good, I only worry slightly about the backend case, made a suggestion.

We still need to figure out the /__clerk/(.*) not being in the matcher by default too. Not sure that's going to affect this PR though, but we should probably figure it out before merging.

Comment threadpackages/shared/src/proxy.ts
) {
// Auto-detect proxy for supported platform deployments (production only)
if (!options.proxyUrl && !options.domain && options.publishableKey?.startsWith('pk_live_')) {
const hostname = clerkRequest.clerkUrl.hostname;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure a comment is enough here (it also doesn't explain why this is currently safe). It's very hard to fully understand the implications of something like this, especially as code changes over time, so I think it doesn't hurt to be defensive.

I wonder if we could straight up reuse getAutoProxyUrlFromEnvironment here (with minor tweaks)? That way, if the backend is deployed to Vercel auto-proxying will work correctly, which should be fine for the use case we are targeting?

If the backend is hosted separately, I think requiring you to configure a proxyUrl for it yourself is fair instead of picking it up from the header?

@Railly

Copy link
Copy Markdown
Contributor

I'm not sure a comment is enough here (it also doesn't explain why this is currently safe). It's very hard to fully understand the implications of something like this, especially as code changes over time, so I think it doesn't hurt to be defensive.
I wonder if we could straight up reuse getAutoProxyUrlFromEnvironment here (with minor tweaks)? That way, if the backend is deployed to Vercel auto-proxying will work correctly, which should be fine for the use case we are targeting?
If the backend is hosted separately, I think requiring you to configure a proxyUrl for it yourself is fair instead of picking it up from the header?

@Ephem addressed in 43be86f, replaced header-based detection with getAutoProxyUrlFromEnvironment in the backend. If deployed outside Vercel, auto-proxy simply doesn't activate and you configure proxyUrl manually.

@EphemEphem left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Ephem addressed in 43be86f, replaced header-based detection with getAutoProxyUrlFromEnvironment in the backend. If deployed outside Vercel, auto-proxy simply doesn't activate and you configure proxyUrl manually.

Awesome, that sidesteps the concern entirely, thanks!

I think this is looking good to go now, great work Bryce on the original implementation and Railly on picking it up!

@nikosdouvlis Do you have any final concerns?

@nikosdouvlisnikosdouvlis left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM :)

@Railly

Copy link
Copy Markdown
Contributor

hey @nikosdouvlis, could you merge this when you get a chance? I’m not the PR author so I don’t have permissions

@nikosdouvlis
nikosdouvlis merged commit 9b57986 into mainApr 28, 2026
177 of 192 checks passed
@nikosdouvlis
nikosdouvlis deleted the brkalow/auto-proxy-vercel-subdomains branch April 28, 2026 21:10
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@brkalow@dominic-clerk@Railly@nikosdouvlis@Ephem
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(*): auto-proxy for eligible hosts - #8035

Merged
nikosdouvlis merged 35 commits into
mainfrom
brkalow/auto-proxy-vercel-subdomains
Apr 28, 2026
Merged

feat(*): auto-proxy for eligible hosts#8035
nikosdouvlis merged 35 commits into
mainfrom
brkalow/auto-proxy-vercel-subdomains

Conversation

@brkalow

@brkalowbrkalow commented Mar 11, 2026

Copy link
Copy Markdown
Member

Description

This PR implements automatic FAPI proxying for Vercel preview deployments. When an app is deployed to a .vercel.app subdomain without explicit proxy or domain configuration, the SDK automatically routes FAPI requests through the app's own domain via the /__clerk proxy path.

The auto-detection is centralized across all SDKs:

  • Shared utilities (@clerk/shared/proxy): New isVercelPreviewDeploy() helper detects .vercel.app hostnames
  • Client-side (@clerk/clerk-js): Auto-detect in proxyUrl getter when window.location.hostname is .vercel.app
  • Server-side (@clerk/backend): Auto-detect in authenticateContext constructor when request hostname is .vercel.app
  • Next.js middleware (@clerk/nextjs): Auto-enable proxy interception for /__clerk/* requests on .vercel.app hostnames

Guard conditions prevent auto-detection when explicit configuration is provided: proxyUrl, domain, or environment variables NEXT_PUBLIC_CLERK_PROXY_URL / NEXT_PUBLIC_CLERK_DOMAIN.

Test Results

  • @clerk/shared: 981/981 tests passed
  • @clerk/backend: 1114/1114 tests passed
  • @clerk/clerk-js: 624/624 tests passed
  • @clerk/nextjs: 362 tests passed (49 pre-existing failures unrelated to this change)

Build verification: All packages build cleanly with no type errors.

Checklist

  • pnpm test runs as expected
  • pnpm build runs as expected
  • Tests added for new functionality

Type of change

  • 🌟 New feature

Summary by CodeRabbit

  • New Features

    • Automatic proxy detection for Vercel-style hosts: when no explicit proxy or domain is provided, the frontend API routing can auto-configure a relative proxy for eligible deployments; explicit proxy or domain settings take precedence.
  • Bug Fixes

    • Improved handling of relative proxy URLs in non-browser/server contexts and script-host resolution fallback.
  • Tests

    • Added comprehensive tests validating auto-detection, precedence rules, environment-based behavior, and various host scenarios.

@vercel

vercelBot commented Mar 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentApr 27, 2026 7:48pm

Request Review

@changeset-bot

changeset-botBot commented Mar 11, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: d0450ed

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 20 packages
NameType
@clerk/sharedPatch
@clerk/backendPatch
@clerk/clerk-jsPatch
@clerk/nextjsPatch
@clerk/astroPatch
@clerk/chrome-extensionPatch
@clerk/expo-passkeysPatch
@clerk/expoPatch
@clerk/expressPatch
@clerk/fastifyPatch
@clerk/honoPatch
@clerk/localizationsPatch
@clerk/mswPatch
@clerk/nuxtPatch
@clerk/react-routerPatch
@clerk/reactPatch
@clerk/tanstack-react-startPatch
@clerk/testingPatch
@clerk/uiPatch
@clerk/vuePatch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitaiBot commented Mar 11, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds exported functions shouldAutoProxy(hostname: string) and getAutoProxyUrlFromEnvironment(...) to the shared package to detect eligible hostnames (e.g., *.vercel.app) and derive a relative proxy path (/__clerk). Integrates this auto-proxy logic into backend AuthenticateContext, clerk-js core, Next.js middleware, and environment-merging utilities so that when neither proxyUrl nor domain is provided, the proxy may be set to the current origin + /__clerk for eligible environments. Adds tests across packages and a changeset entry.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 20.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title 'feat(*): auto-proxy for eligible hosts' accurately describes the main feature being implemented across the codebase - automatic proxy detection for eligible hostnames (specifically .vercel.app subdomains).

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

…omains
Add automatic FAPI proxying detection for Vercel preview deployments across all SDKs. When an app is deployed to a .vercel.app subdomain without explicit proxy/domain configuration, the SDK automatically routes FAPI requests through the app's own domain via /__clerk proxy path.
- Add isVercelPreviewDeploy() helper in @clerk/shared/proxy
- Auto-detect in clerk-js proxyUrl getter for client-side SDK initialization
- Auto-detect in @clerk/backend authenticateContext for server-side auth
- Enable proxy interception in Next.js middleware for /__clerk/* requests on .vercel.app
- Add comprehensive tests for all three layers (shared, backend, clerk-js, nextjs)
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
@pkg-pr-new

pkg-pr-newBot commented Mar 13, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

npm i https://pkg.pr.new/@clerk/astro@8035

@clerk/backend

npm i https://pkg.pr.new/@clerk/backend@8035

@clerk/chrome-extension

npm i https://pkg.pr.new/@clerk/chrome-extension@8035

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@8035

@clerk/dev-cli

npm i https://pkg.pr.new/@clerk/dev-cli@8035

@clerk/expo

npm i https://pkg.pr.new/@clerk/expo@8035

@clerk/expo-passkeys

npm i https://pkg.pr.new/@clerk/expo-passkeys@8035

@clerk/express

npm i https://pkg.pr.new/@clerk/express@8035

@clerk/fastify

npm i https://pkg.pr.new/@clerk/fastify@8035

@clerk/hono

npm i https://pkg.pr.new/@clerk/hono@8035

@clerk/localizations

npm i https://pkg.pr.new/@clerk/localizations@8035

@clerk/nextjs

npm i https://pkg.pr.new/@clerk/nextjs@8035

@clerk/nuxt

npm i https://pkg.pr.new/@clerk/nuxt@8035

@clerk/react

npm i https://pkg.pr.new/@clerk/react@8035

@clerk/react-router

npm i https://pkg.pr.new/@clerk/react-router@8035

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@8035

@clerk/tanstack-react-start

npm i https://pkg.pr.new/@clerk/tanstack-react-start@8035

@clerk/testing

npm i https://pkg.pr.new/@clerk/testing@8035

@clerk/ui

npm i https://pkg.pr.new/@clerk/ui@8035

@clerk/upgrade

npm i https://pkg.pr.new/@clerk/upgrade@8035

@clerk/vue

npm i https://pkg.pr.new/@clerk/vue@8035

commit: d0450ed

@brkalowbrkalow changed the title Auto-proxy for .vercel.app subdomainsfeat(*): auto-proxy for eligible hostsMar 13, 2026

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.changeset/tiny-badgers-smile.md:
- Line 8: Update the changeset description sentence that currently reads "Add
auto-proxy detection for eligible hosts and generalize the internal helper
naming for future providers." to be specific about which hosts are considered
eligible (e.g., Vercel preview deployments) and to clarify the helper change;
for example, change the description to: "Add auto-proxy detection for Vercel
preview deployments (.vercel.app subdomains) and generalize the internal helper
for future hosting providers." Locate and edit the line containing that
description in the changeset (the sentence beginning "Add auto-proxy detection
for eligible hosts...") and replace it with the clearer, user-facing wording.
- Around line 1-6: The changeset incorrectly marks package bumps as 'patch' but
a new public API (shouldAutoProxy) was added; update the entries so any package
that exports the new function (at least '@clerk/shared', and any packages that
re-export it like '@clerk/backend', '@clerk/clerk-js', '@clerk/nextjs' if
applicable) use 'minor' instead of 'patch' in the
.changeset/tiny-badgers-smile.md file; ensure the header lines for those package
entries read 'minor' to reflect the new public API bump while keeping other
metadata unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0cecde51-9659-43c2-8677-37774a033a71

📥 Commits

Reviewing files that changed from the base of the PR and between dbc580e and 2d729d2.

📒 Files selected for processing (1)
  • .changeset/tiny-badgers-smile.md

Comment thread.changeset/tiny-badgers-smile.md Outdated
Comment on lines +1 to +6
---
'@clerk/backend': patch
'@clerk/clerk-js': patch
'@clerk/nextjs': patch
'@clerk/shared': patch
---

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash# Description: Check if shouldAutoProxy is a public export# Search for shouldAutoProxy exports in the shared package
rg -n "export.*shouldAutoProxy" packages/shared/

Repository: clerk/javascript

Length of output: 152


Update version bumps from patch to minor to reflect the new public API export.

The changeset marks all packages for patch bumps, but the PR introduces a new public API function shouldAutoProxy exported from @clerk/shared. Per semantic versioning standards, new public APIs require minor version bumps, not patch (which is reserved for bug fixes). Update the changeset accordingly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.changeset/tiny-badgers-smile.md around lines 1 - 6, The changeset
incorrectly marks package bumps as 'patch' but a new public API
(shouldAutoProxy) was added; update the entries so any package that exports the
new function (at least '@clerk/shared', and any packages that re-export it like
'@clerk/backend', '@clerk/clerk-js', '@clerk/nextjs' if applicable) use 'minor'
instead of 'patch' in the .changeset/tiny-badgers-smile.md file; ensure the
header lines for those package entries read 'minor' to reflect the new public
API bump while keeping other metadata unchanged.

Comment thread.changeset/tiny-badgers-smile.md Outdated
@brkalow

Copy link
Copy Markdown
MemberAuthor

!snapshot

@github-actions

This comment has been minimized.

@brkalow

Copy link
Copy Markdown
MemberAuthor

!snapshot

@github-actions

This comment has been minimized.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/shared/src/proxy.ts`:
- Around line 45-47: The normalizeHostname function can throw when called with a
malformed URL (e.g., VERCEL_PROJECT_PRODUCTION_URL); wrap the URL parsing in a
try-catch inside normalizeHostname (the function shown using new
URL(...).hostname) and return an empty string on error so config resolution
doesn't crash; ensure callers that rely on normalizeHostname (where
VERCEL_PROJECT_PRODUCTION_URL is passed) can handle an empty string result
gracefully.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: 2ad9a329-ea13-4ffe-86f9-f7dec3b163e7

📥 Commits

Reviewing files that changed from the base of the PR and between 2d729d2 and 72d6bdc.

📒 Files selected for processing (5)
  • .changeset/tiny-badgers-smile.md
  • packages/nextjs/src/utils/__tests__/mergeNextClerkPropsWithEnv.test.ts
  • packages/nextjs/src/utils/mergeNextClerkPropsWithEnv.ts
  • packages/shared/src/__tests__/proxy.spec.ts
  • packages/shared/src/proxy.ts

Comment threadpackages/shared/src/proxy.ts Outdated
Comment on lines +45 to +47
function normalizeHostname(hostnameOrUrl: string): string {
if (hostnameOrUrl.startsWith('http://') || hostnameOrUrl.startsWith('https://')) {
return new URL(hostnameOrUrl).hostname;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash# Verify call path and URL constructor throw behavior
rg -n "normalizeHostname|getAutoProxyUrlFromEnvironment|VERCEL_PROJECT_PRODUCTION_URL" \
packages/shared/src/proxy.ts packages/nextjs/src/utils/mergeNextClerkPropsWithEnv.ts
node - <<'NODE'const samples = ['https://', 'https://?x=1', 'https:///foo', 'https://myapp.vercel.app'];for (const s of samples) { try { console.log(`${s} => hostname="${new URL(s).hostname}"`); } catch (e) { console.log(`${s} => THROWS (${e.name})`); }}NODE

Repository: clerk/javascript

Length of output: 853


🏁 Script executed:

#!/bin/bash# Get full function context at line 45-47 and surrounding linesecho"=== normalizeHostname function context (lines 41-55) ==="
sed -n '41,55p' packages/shared/src/proxy.ts | cat -n
echo -e "\n=== getAutoProxyUrlFromEnvironment call context (lines 70-80) ==="
sed -n '70,80p' packages/shared/src/proxy.ts | cat -n
# Search for tests related to proxy.ts or normalizeHostnameecho -e "\n=== Tests for proxy functions ==="
find . -name "*.test.ts" -o -name "*.spec.ts"| xargs grep -l "normalizeHostname\|getAutoProxyUrlFromEnvironment\|proxy"2>/dev/null | head -10

Repository: clerk/javascript

Length of output: 1707


🏁 Script executed:

#!/bin/bash# Check if there are tests covering error cases for these functionsecho"=== Searching for test files related to proxy ==="
fd -e "test.ts" -e "spec.ts"| xargs grep -l "normalizeHostname\|getAutoProxyUrlFromEnvironment"2>/dev/null
# Also check the test directory structureecho -e "\n=== Checking __tests__ and test directory structure ==="
find packages/shared -type d \( -name "__tests__" -o -name "tests" -o -name "test"\)| head -5

Repository: clerk/javascript

Length of output: 410


🏁 Script executed:

#!/bin/bash# Read the existing proxy test file to see current coverageecho"=== Existing proxy tests ==="
cat packages/shared/src/__tests__/proxy.spec.ts

Repository: clerk/javascript

Length of output: 5211


Handle malformed VERCEL_PROJECT_PRODUCTION_URL in hostname normalization to prevent runtime crashes.

At line 76, normalizeHostname() is called with an environment variable without error handling. If VERCEL_PROJECT_PRODUCTION_URL is malformed (e.g., https:// or https://?x=1), new URL() throws a TypeError, crashing config resolution instead of gracefully returning an empty string. Add try-catch to safely handle this:

Proposed fix
function normalizeHostname(hostnameOrUrl: string): string {
if (hostnameOrUrl.startsWith('http://') || hostnameOrUrl.startsWith('https://')) {
- return new URL(hostnameOrUrl).hostname;+ try {+ return new URL(hostnameOrUrl).hostname;+ } catch {+ return '';+ }
}
return hostnameOrUrl.split('/')[0] || '';
}

Also applies to: 76-77

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/shared/src/proxy.ts` around lines 45 - 47, The normalizeHostname
function can throw when called with a malformed URL (e.g.,
VERCEL_PROJECT_PRODUCTION_URL); wrap the URL parsing in a try-catch inside
normalizeHostname (the function shown using new URL(...).hostname) and return an
empty string on error so config resolution doesn't crash; ensure callers that
rely on normalizeHostname (where VERCEL_PROJECT_PRODUCTION_URL is passed) can
handle an empty string result gracefully.

@brkalow

Copy link
Copy Markdown
MemberAuthor

!snapshot

@github-actions

This comment has been minimized.

@Railly

Copy link
Copy Markdown
Contributor

!snapshot

@github-actions

Copy link
Copy Markdown
Contributor

Hey @Railly - the snapshot version command generated the following package versions:

PackageVersion
@clerk/astro3.0.18-snapshot.v20260421194054
@clerk/backend3.2.14-snapshot.v20260421194054
@clerk/chrome-extension3.1.15-snapshot.v20260421194054
@clerk/clerk-js6.7.5-snapshot.v20260421194054
@clerk/dev-cli0.1.1-snapshot.v20260421194054
@clerk/expo3.2.2-snapshot.v20260421194054
@clerk/expo-passkeys1.0.15-snapshot.v20260421194054
@clerk/express2.1.6-snapshot.v20260421194054
@clerk/fastify3.1.16-snapshot.v20260421194054
@clerk/hono0.1.16-snapshot.v20260421194054
@clerk/localizations4.5.3-snapshot.v20260421194054
@clerk/msw0.0.15-snapshot.v20260421194054
@clerk/nextjs7.2.4-snapshot.v20260421194054
@clerk/nuxt2.2.5-snapshot.v20260421194054
@clerk/react6.4.3-snapshot.v20260421194054
@clerk/react-router3.1.4-snapshot.v20260421194054
@clerk/shared4.8.3-snapshot.v20260421194054
@clerk/tanstack-react-start1.1.4-snapshot.v20260421194054
@clerk/testing2.0.18-snapshot.v20260421194054
@clerk/ui1.6.4-snapshot.v20260421194054
@clerk/upgrade2.0.3-snapshot.v20260421194054
@clerk/vue2.0.16-snapshot.v20260421194054

Tip: Use the snippet copy button below to quickly install the required packages.
@clerk/astro

npm i @clerk/astro@3.0.18-snapshot.v20260421194054 --save-exact

@clerk/backend

npm i @clerk/backend@3.2.14-snapshot.v20260421194054 --save-exact

@clerk/chrome-extension

npm i @clerk/chrome-extension@3.1.15-snapshot.v20260421194054 --save-exact

@clerk/clerk-js

npm i @clerk/clerk-js@6.7.5-snapshot.v20260421194054 --save-exact

@clerk/dev-cli

npm i @clerk/dev-cli@0.1.1-snapshot.v20260421194054 --save-exact

@clerk/expo

npm i @clerk/expo@3.2.2-snapshot.v20260421194054 --save-exact

@clerk/expo-passkeys

npm i @clerk/expo-passkeys@1.0.15-snapshot.v20260421194054 --save-exact

@clerk/express

npm i @clerk/express@2.1.6-snapshot.v20260421194054 --save-exact

@clerk/fastify

npm i @clerk/fastify@3.1.16-snapshot.v20260421194054 --save-exact

@clerk/hono

npm i @clerk/hono@0.1.16-snapshot.v20260421194054 --save-exact

@clerk/localizations

npm i @clerk/localizations@4.5.3-snapshot.v20260421194054 --save-exact

@clerk/msw

npm i @clerk/msw@0.0.15-snapshot.v20260421194054 --save-exact

@clerk/nextjs

npm i @clerk/nextjs@7.2.4-snapshot.v20260421194054 --save-exact

@clerk/nuxt

npm i @clerk/nuxt@2.2.5-snapshot.v20260421194054 --save-exact

@clerk/react

npm i @clerk/react@6.4.3-snapshot.v20260421194054 --save-exact

@clerk/react-router

npm i @clerk/react-router@3.1.4-snapshot.v20260421194054 --save-exact

@clerk/shared

npm i @clerk/shared@4.8.3-snapshot.v20260421194054 --save-exact

@clerk/tanstack-react-start

npm i @clerk/tanstack-react-start@1.1.4-snapshot.v20260421194054 --save-exact

@clerk/testing

npm i @clerk/testing@2.0.18-snapshot.v20260421194054 --save-exact

@clerk/ui

npm i @clerk/ui@1.6.4-snapshot.v20260421194054 --save-exact

@clerk/upgrade

npm i @clerk/upgrade@2.0.3-snapshot.v20260421194054 --save-exact

@clerk/vue

npm i @clerk/vue@2.0.16-snapshot.v20260421194054 --save-exact

@Railly

Copy link
Copy Markdown
Contributor

@Ephem Tested with a real production instance on vercel.app, auth works end-to-end.

One finding: the default middleware matcher excludes .js files, which prevents /__clerk/npm/@clerk/clerk-js@6/dist/clerk.browser.js from reaching the middleware. Had to add "/__clerk/(.*)" to the matcher manually. Since auto-proxy is meant to work without config, I think clerkMiddleware should handle this automatically or the default matcher should always include __clerk.

Test app: https://v0-auto-proxy-test.vercel.app

@EphemEphem left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is looking really good, I only worry slightly about the backend case, made a suggestion.

We still need to figure out the /__clerk/(.*) not being in the matcher by default too. Not sure that's going to affect this PR though, but we should probably figure it out before merging.

Comment threadpackages/shared/src/proxy.ts
) {
// Auto-detect proxy for supported platform deployments (production only)
if (!options.proxyUrl && !options.domain && options.publishableKey?.startsWith('pk_live_')) {
const hostname = clerkRequest.clerkUrl.hostname;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure a comment is enough here (it also doesn't explain why this is currently safe). It's very hard to fully understand the implications of something like this, especially as code changes over time, so I think it doesn't hurt to be defensive.

I wonder if we could straight up reuse getAutoProxyUrlFromEnvironment here (with minor tweaks)? That way, if the backend is deployed to Vercel auto-proxying will work correctly, which should be fine for the use case we are targeting?

If the backend is hosted separately, I think requiring you to configure a proxyUrl for it yourself is fair instead of picking it up from the header?

@Railly

Copy link
Copy Markdown
Contributor

I'm not sure a comment is enough here (it also doesn't explain why this is currently safe). It's very hard to fully understand the implications of something like this, especially as code changes over time, so I think it doesn't hurt to be defensive.
I wonder if we could straight up reuse getAutoProxyUrlFromEnvironment here (with minor tweaks)? That way, if the backend is deployed to Vercel auto-proxying will work correctly, which should be fine for the use case we are targeting?
If the backend is hosted separately, I think requiring you to configure a proxyUrl for it yourself is fair instead of picking it up from the header?

@Ephem addressed in 43be86f, replaced header-based detection with getAutoProxyUrlFromEnvironment in the backend. If deployed outside Vercel, auto-proxy simply doesn't activate and you configure proxyUrl manually.

@EphemEphem left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Ephem addressed in 43be86f, replaced header-based detection with getAutoProxyUrlFromEnvironment in the backend. If deployed outside Vercel, auto-proxy simply doesn't activate and you configure proxyUrl manually.

Awesome, that sidesteps the concern entirely, thanks!

I think this is looking good to go now, great work Bryce on the original implementation and Railly on picking it up!

@nikosdouvlis Do you have any final concerns?

@nikosdouvlisnikosdouvlis left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM :)

@Railly

Copy link
Copy Markdown
Contributor

hey @nikosdouvlis, could you merge this when you get a chance? I’m not the PR author so I don’t have permissions

@nikosdouvlis
nikosdouvlis merged commit 9b57986 into mainApr 28, 2026
177 of 192 checks passed
@nikosdouvlis
nikosdouvlis deleted the brkalow/auto-proxy-vercel-subdomains branch April 28, 2026 21:10
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@brkalow@dominic-clerk@Railly@nikosdouvlis@Ephem
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

feat(*): auto-proxy for eligible hosts - #8035

Merged
nikosdouvlis merged 35 commits into
mainfrom
brkalow/auto-proxy-vercel-subdomains
Apr 28, 2026
Merged

feat(*): auto-proxy for eligible hosts#8035
nikosdouvlis merged 35 commits into
mainfrom
brkalow/auto-proxy-vercel-subdomains

Conversation

@brkalow

@brkalowbrkalow commented Mar 11, 2026

Copy link
Copy Markdown
Member

Description

This PR implements automatic FAPI proxying for Vercel preview deployments. When an app is deployed to a .vercel.app subdomain without explicit proxy or domain configuration, the SDK automatically routes FAPI requests through the app's own domain via the /__clerk proxy path.

The auto-detection is centralized across all SDKs:

  • Shared utilities (@clerk/shared/proxy): New isVercelPreviewDeploy() helper detects .vercel.app hostnames
  • Client-side (@clerk/clerk-js): Auto-detect in proxyUrl getter when window.location.hostname is .vercel.app
  • Server-side (@clerk/backend): Auto-detect in authenticateContext constructor when request hostname is .vercel.app
  • Next.js middleware (@clerk/nextjs): Auto-enable proxy interception for /__clerk/* requests on .vercel.app hostnames

Guard conditions prevent auto-detection when explicit configuration is provided: proxyUrl, domain, or environment variables NEXT_PUBLIC_CLERK_PROXY_URL / NEXT_PUBLIC_CLERK_DOMAIN.

Test Results

  • @clerk/shared: 981/981 tests passed
  • @clerk/backend: 1114/1114 tests passed
  • @clerk/clerk-js: 624/624 tests passed
  • @clerk/nextjs: 362 tests passed (49 pre-existing failures unrelated to this change)

Build verification: All packages build cleanly with no type errors.

Checklist

  • pnpm test runs as expected
  • pnpm build runs as expected
  • Tests added for new functionality

Type of change

  • 🌟 New feature

Summary by CodeRabbit

  • New Features

    • Automatic proxy detection for Vercel-style hosts: when no explicit proxy or domain is provided, the frontend API routing can auto-configure a relative proxy for eligible deployments; explicit proxy or domain settings take precedence.
  • Bug Fixes

    • Improved handling of relative proxy URLs in non-browser/server contexts and script-host resolution fallback.
  • Tests

    • Added comprehensive tests validating auto-detection, precedence rules, environment-based behavior, and various host scenarios.

@vercel

vercelBot commented Mar 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentActionsUpdated (UTC)
clerk-js-sandboxReadyReadyPreview, CommentApr 27, 2026 7:48pm

Request Review

@changeset-bot

changeset-botBot commented Mar 11, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: d0450ed

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 20 packages
NameType
@clerk/sharedPatch
@clerk/backendPatch
@clerk/clerk-jsPatch
@clerk/nextjsPatch
@clerk/astroPatch
@clerk/chrome-extensionPatch
@clerk/expo-passkeysPatch
@clerk/expoPatch
@clerk/expressPatch
@clerk/fastifyPatch
@clerk/honoPatch
@clerk/localizationsPatch
@clerk/mswPatch
@clerk/nuxtPatch
@clerk/react-routerPatch
@clerk/reactPatch
@clerk/tanstack-react-startPatch
@clerk/testingPatch
@clerk/uiPatch
@clerk/vuePatch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitaiBot commented Mar 11, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds exported functions shouldAutoProxy(hostname: string) and getAutoProxyUrlFromEnvironment(...) to the shared package to detect eligible hostnames (e.g., *.vercel.app) and derive a relative proxy path (/__clerk). Integrates this auto-proxy logic into backend AuthenticateContext, clerk-js core, Next.js middleware, and environment-merging utilities so that when neither proxyUrl nor domain is provided, the proxy may be set to the current origin + /__clerk for eligible environments. Adds tests across packages and a changeset entry.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check nameStatusExplanationResolution
Docstring Coverage⚠️ WarningDocstring coverage is 20.00% which is insufficient. The required threshold is 80.00%.Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check nameStatusExplanation
Description Check✅ PassedCheck skipped - CodeRabbit’s high-level summary is enabled.
Title check✅ PassedThe title 'feat(*): auto-proxy for eligible hosts' accurately describes the main feature being implemented across the codebase - automatic proxy detection for eligible hostnames (specifically .vercel.app subdomains).

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

…omains
Add automatic FAPI proxying detection for Vercel preview deployments across all SDKs. When an app is deployed to a .vercel.app subdomain without explicit proxy/domain configuration, the SDK automatically routes FAPI requests through the app's own domain via /__clerk proxy path.
- Add isVercelPreviewDeploy() helper in @clerk/shared/proxy
- Auto-detect in clerk-js proxyUrl getter for client-side SDK initialization
- Auto-detect in @clerk/backend authenticateContext for server-side auth
- Enable proxy interception in Next.js middleware for /__clerk/* requests on .vercel.app
- Add comprehensive tests for all three layers (shared, backend, clerk-js, nextjs)
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
@pkg-pr-new

pkg-pr-newBot commented Mar 13, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

npm i https://pkg.pr.new/@clerk/astro@8035

@clerk/backend

npm i https://pkg.pr.new/@clerk/backend@8035

@clerk/chrome-extension

npm i https://pkg.pr.new/@clerk/chrome-extension@8035

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@8035

@clerk/dev-cli

npm i https://pkg.pr.new/@clerk/dev-cli@8035

@clerk/expo

npm i https://pkg.pr.new/@clerk/expo@8035

@clerk/expo-passkeys

npm i https://pkg.pr.new/@clerk/expo-passkeys@8035

@clerk/express

npm i https://pkg.pr.new/@clerk/express@8035

@clerk/fastify

npm i https://pkg.pr.new/@clerk/fastify@8035

@clerk/hono

npm i https://pkg.pr.new/@clerk/hono@8035

@clerk/localizations

npm i https://pkg.pr.new/@clerk/localizations@8035

@clerk/nextjs

npm i https://pkg.pr.new/@clerk/nextjs@8035

@clerk/nuxt

npm i https://pkg.pr.new/@clerk/nuxt@8035

@clerk/react

npm i https://pkg.pr.new/@clerk/react@8035

@clerk/react-router

npm i https://pkg.pr.new/@clerk/react-router@8035

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@8035

@clerk/tanstack-react-start

npm i https://pkg.pr.new/@clerk/tanstack-react-start@8035

@clerk/testing

npm i https://pkg.pr.new/@clerk/testing@8035

@clerk/ui

npm i https://pkg.pr.new/@clerk/ui@8035

@clerk/upgrade

npm i https://pkg.pr.new/@clerk/upgrade@8035

@clerk/vue

npm i https://pkg.pr.new/@clerk/vue@8035

commit: d0450ed

@brkalowbrkalow changed the title Auto-proxy for .vercel.app subdomainsfeat(*): auto-proxy for eligible hostsMar 13, 2026

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.changeset/tiny-badgers-smile.md:
- Line 8: Update the changeset description sentence that currently reads "Add
auto-proxy detection for eligible hosts and generalize the internal helper
naming for future providers." to be specific about which hosts are considered
eligible (e.g., Vercel preview deployments) and to clarify the helper change;
for example, change the description to: "Add auto-proxy detection for Vercel
preview deployments (.vercel.app subdomains) and generalize the internal helper
for future hosting providers." Locate and edit the line containing that
description in the changeset (the sentence beginning "Add auto-proxy detection
for eligible hosts...") and replace it with the clearer, user-facing wording.
- Around line 1-6: The changeset incorrectly marks package bumps as 'patch' but
a new public API (shouldAutoProxy) was added; update the entries so any package
that exports the new function (at least '@clerk/shared', and any packages that
re-export it like '@clerk/backend', '@clerk/clerk-js', '@clerk/nextjs' if
applicable) use 'minor' instead of 'patch' in the
.changeset/tiny-badgers-smile.md file; ensure the header lines for those package
entries read 'minor' to reflect the new public API bump while keeping other
metadata unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0cecde51-9659-43c2-8677-37774a033a71

📥 Commits

Reviewing files that changed from the base of the PR and between dbc580e and 2d729d2.

📒 Files selected for processing (1)
  • .changeset/tiny-badgers-smile.md

Comment thread.changeset/tiny-badgers-smile.md Outdated
Comment on lines +1 to +6
---
'@clerk/backend': patch
'@clerk/clerk-js': patch
'@clerk/nextjs': patch
'@clerk/shared': patch
---

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash# Description: Check if shouldAutoProxy is a public export# Search for shouldAutoProxy exports in the shared package
rg -n "export.*shouldAutoProxy" packages/shared/

Repository: clerk/javascript

Length of output: 152


Update version bumps from patch to minor to reflect the new public API export.

The changeset marks all packages for patch bumps, but the PR introduces a new public API function shouldAutoProxy exported from @clerk/shared. Per semantic versioning standards, new public APIs require minor version bumps, not patch (which is reserved for bug fixes). Update the changeset accordingly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In @.changeset/tiny-badgers-smile.md around lines 1 - 6, The changeset
incorrectly marks package bumps as 'patch' but a new public API
(shouldAutoProxy) was added; update the entries so any package that exports the
new function (at least '@clerk/shared', and any packages that re-export it like
'@clerk/backend', '@clerk/clerk-js', '@clerk/nextjs' if applicable) use 'minor'
instead of 'patch' in the .changeset/tiny-badgers-smile.md file; ensure the
header lines for those package entries read 'minor' to reflect the new public
API bump while keeping other metadata unchanged.

Comment thread.changeset/tiny-badgers-smile.md Outdated
@brkalow

Copy link
Copy Markdown
MemberAuthor

!snapshot

@github-actions

This comment has been minimized.

@brkalow

Copy link
Copy Markdown
MemberAuthor

!snapshot

@github-actions

This comment has been minimized.

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/shared/src/proxy.ts`:
- Around line 45-47: The normalizeHostname function can throw when called with a
malformed URL (e.g., VERCEL_PROJECT_PRODUCTION_URL); wrap the URL parsing in a
try-catch inside normalizeHostname (the function shown using new
URL(...).hostname) and return an empty string on error so config resolution
doesn't crash; ensure callers that rely on normalizeHostname (where
VERCEL_PROJECT_PRODUCTION_URL is passed) can handle an empty string result
gracefully.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: 2ad9a329-ea13-4ffe-86f9-f7dec3b163e7

📥 Commits

Reviewing files that changed from the base of the PR and between 2d729d2 and 72d6bdc.

📒 Files selected for processing (5)
  • .changeset/tiny-badgers-smile.md
  • packages/nextjs/src/utils/__tests__/mergeNextClerkPropsWithEnv.test.ts
  • packages/nextjs/src/utils/mergeNextClerkPropsWithEnv.ts
  • packages/shared/src/__tests__/proxy.spec.ts
  • packages/shared/src/proxy.ts

Comment threadpackages/shared/src/proxy.ts Outdated
Comment on lines +45 to +47
function normalizeHostname(hostnameOrUrl: string): string {
if (hostnameOrUrl.startsWith('http://') || hostnameOrUrl.startsWith('https://')) {
return new URL(hostnameOrUrl).hostname;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash# Verify call path and URL constructor throw behavior
rg -n "normalizeHostname|getAutoProxyUrlFromEnvironment|VERCEL_PROJECT_PRODUCTION_URL" \
packages/shared/src/proxy.ts packages/nextjs/src/utils/mergeNextClerkPropsWithEnv.ts
node - <<'NODE'const samples = ['https://', 'https://?x=1', 'https:///foo', 'https://myapp.vercel.app'];for (const s of samples) { try { console.log(`${s} => hostname="${new URL(s).hostname}"`); } catch (e) { console.log(`${s} => THROWS (${e.name})`); }}NODE

Repository: clerk/javascript

Length of output: 853


🏁 Script executed:

#!/bin/bash# Get full function context at line 45-47 and surrounding linesecho"=== normalizeHostname function context (lines 41-55) ==="
sed -n '41,55p' packages/shared/src/proxy.ts | cat -n
echo -e "\n=== getAutoProxyUrlFromEnvironment call context (lines 70-80) ==="
sed -n '70,80p' packages/shared/src/proxy.ts | cat -n
# Search for tests related to proxy.ts or normalizeHostnameecho -e "\n=== Tests for proxy functions ==="
find . -name "*.test.ts" -o -name "*.spec.ts"| xargs grep -l "normalizeHostname\|getAutoProxyUrlFromEnvironment\|proxy"2>/dev/null | head -10

Repository: clerk/javascript

Length of output: 1707


🏁 Script executed:

#!/bin/bash# Check if there are tests covering error cases for these functionsecho"=== Searching for test files related to proxy ==="
fd -e "test.ts" -e "spec.ts"| xargs grep -l "normalizeHostname\|getAutoProxyUrlFromEnvironment"2>/dev/null
# Also check the test directory structureecho -e "\n=== Checking __tests__ and test directory structure ==="
find packages/shared -type d \( -name "__tests__" -o -name "tests" -o -name "test"\)| head -5

Repository: clerk/javascript

Length of output: 410


🏁 Script executed:

#!/bin/bash# Read the existing proxy test file to see current coverageecho"=== Existing proxy tests ==="
cat packages/shared/src/__tests__/proxy.spec.ts

Repository: clerk/javascript

Length of output: 5211


Handle malformed VERCEL_PROJECT_PRODUCTION_URL in hostname normalization to prevent runtime crashes.

At line 76, normalizeHostname() is called with an environment variable without error handling. If VERCEL_PROJECT_PRODUCTION_URL is malformed (e.g., https:// or https://?x=1), new URL() throws a TypeError, crashing config resolution instead of gracefully returning an empty string. Add try-catch to safely handle this:

Proposed fix
function normalizeHostname(hostnameOrUrl: string): string {
if (hostnameOrUrl.startsWith('http://') || hostnameOrUrl.startsWith('https://')) {
- return new URL(hostnameOrUrl).hostname;+ try {+ return new URL(hostnameOrUrl).hostname;+ } catch {+ return '';+ }
}
return hostnameOrUrl.split('/')[0] || '';
}

Also applies to: 76-77

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@packages/shared/src/proxy.ts` around lines 45 - 47, The normalizeHostname
function can throw when called with a malformed URL (e.g.,
VERCEL_PROJECT_PRODUCTION_URL); wrap the URL parsing in a try-catch inside
normalizeHostname (the function shown using new URL(...).hostname) and return an
empty string on error so config resolution doesn't crash; ensure callers that
rely on normalizeHostname (where VERCEL_PROJECT_PRODUCTION_URL is passed) can
handle an empty string result gracefully.

@brkalow

Copy link
Copy Markdown
MemberAuthor

!snapshot

@github-actions

This comment has been minimized.

@Railly

Copy link
Copy Markdown
Contributor

!snapshot

@github-actions

Copy link
Copy Markdown
Contributor

Hey @Railly - the snapshot version command generated the following package versions:

PackageVersion
@clerk/astro3.0.18-snapshot.v20260421194054
@clerk/backend3.2.14-snapshot.v20260421194054
@clerk/chrome-extension3.1.15-snapshot.v20260421194054
@clerk/clerk-js6.7.5-snapshot.v20260421194054
@clerk/dev-cli0.1.1-snapshot.v20260421194054
@clerk/expo3.2.2-snapshot.v20260421194054
@clerk/expo-passkeys1.0.15-snapshot.v20260421194054
@clerk/express2.1.6-snapshot.v20260421194054
@clerk/fastify3.1.16-snapshot.v20260421194054
@clerk/hono0.1.16-snapshot.v20260421194054
@clerk/localizations4.5.3-snapshot.v20260421194054
@clerk/msw0.0.15-snapshot.v20260421194054
@clerk/nextjs7.2.4-snapshot.v20260421194054
@clerk/nuxt2.2.5-snapshot.v20260421194054
@clerk/react6.4.3-snapshot.v20260421194054
@clerk/react-router3.1.4-snapshot.v20260421194054
@clerk/shared4.8.3-snapshot.v20260421194054
@clerk/tanstack-react-start1.1.4-snapshot.v20260421194054
@clerk/testing2.0.18-snapshot.v20260421194054
@clerk/ui1.6.4-snapshot.v20260421194054
@clerk/upgrade2.0.3-snapshot.v20260421194054
@clerk/vue2.0.16-snapshot.v20260421194054

Tip: Use the snippet copy button below to quickly install the required packages.
@clerk/astro

npm i @clerk/astro@3.0.18-snapshot.v20260421194054 --save-exact

@clerk/backend

npm i @clerk/backend@3.2.14-snapshot.v20260421194054 --save-exact

@clerk/chrome-extension

npm i @clerk/chrome-extension@3.1.15-snapshot.v20260421194054 --save-exact

@clerk/clerk-js

npm i @clerk/clerk-js@6.7.5-snapshot.v20260421194054 --save-exact

@clerk/dev-cli

npm i @clerk/dev-cli@0.1.1-snapshot.v20260421194054 --save-exact

@clerk/expo

npm i @clerk/expo@3.2.2-snapshot.v20260421194054 --save-exact

@clerk/expo-passkeys

npm i @clerk/expo-passkeys@1.0.15-snapshot.v20260421194054 --save-exact

@clerk/express

npm i @clerk/express@2.1.6-snapshot.v20260421194054 --save-exact

@clerk/fastify

npm i @clerk/fastify@3.1.16-snapshot.v20260421194054 --save-exact

@clerk/hono

npm i @clerk/hono@0.1.16-snapshot.v20260421194054 --save-exact

@clerk/localizations

npm i @clerk/localizations@4.5.3-snapshot.v20260421194054 --save-exact

@clerk/msw

npm i @clerk/msw@0.0.15-snapshot.v20260421194054 --save-exact

@clerk/nextjs

npm i @clerk/nextjs@7.2.4-snapshot.v20260421194054 --save-exact

@clerk/nuxt

npm i @clerk/nuxt@2.2.5-snapshot.v20260421194054 --save-exact

@clerk/react

npm i @clerk/react@6.4.3-snapshot.v20260421194054 --save-exact

@clerk/react-router

npm i @clerk/react-router@3.1.4-snapshot.v20260421194054 --save-exact

@clerk/shared

npm i @clerk/shared@4.8.3-snapshot.v20260421194054 --save-exact

@clerk/tanstack-react-start

npm i @clerk/tanstack-react-start@1.1.4-snapshot.v20260421194054 --save-exact

@clerk/testing

npm i @clerk/testing@2.0.18-snapshot.v20260421194054 --save-exact

@clerk/ui

npm i @clerk/ui@1.6.4-snapshot.v20260421194054 --save-exact

@clerk/upgrade

npm i @clerk/upgrade@2.0.3-snapshot.v20260421194054 --save-exact

@clerk/vue

npm i @clerk/vue@2.0.16-snapshot.v20260421194054 --save-exact

@Railly

Copy link
Copy Markdown
Contributor

@Ephem Tested with a real production instance on vercel.app, auth works end-to-end.

One finding: the default middleware matcher excludes .js files, which prevents /__clerk/npm/@clerk/clerk-js@6/dist/clerk.browser.js from reaching the middleware. Had to add "/__clerk/(.*)" to the matcher manually. Since auto-proxy is meant to work without config, I think clerkMiddleware should handle this automatically or the default matcher should always include __clerk.

Test app: https://v0-auto-proxy-test.vercel.app

@EphemEphem left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is looking really good, I only worry slightly about the backend case, made a suggestion.

We still need to figure out the /__clerk/(.*) not being in the matcher by default too. Not sure that's going to affect this PR though, but we should probably figure it out before merging.

Comment threadpackages/shared/src/proxy.ts
) {
// Auto-detect proxy for supported platform deployments (production only)
if (!options.proxyUrl && !options.domain && options.publishableKey?.startsWith('pk_live_')) {
const hostname = clerkRequest.clerkUrl.hostname;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure a comment is enough here (it also doesn't explain why this is currently safe). It's very hard to fully understand the implications of something like this, especially as code changes over time, so I think it doesn't hurt to be defensive.

I wonder if we could straight up reuse getAutoProxyUrlFromEnvironment here (with minor tweaks)? That way, if the backend is deployed to Vercel auto-proxying will work correctly, which should be fine for the use case we are targeting?

If the backend is hosted separately, I think requiring you to configure a proxyUrl for it yourself is fair instead of picking it up from the header?

@Railly

Copy link
Copy Markdown
Contributor

I'm not sure a comment is enough here (it also doesn't explain why this is currently safe). It's very hard to fully understand the implications of something like this, especially as code changes over time, so I think it doesn't hurt to be defensive.
I wonder if we could straight up reuse getAutoProxyUrlFromEnvironment here (with minor tweaks)? That way, if the backend is deployed to Vercel auto-proxying will work correctly, which should be fine for the use case we are targeting?
If the backend is hosted separately, I think requiring you to configure a proxyUrl for it yourself is fair instead of picking it up from the header?

@Ephem addressed in 43be86f, replaced header-based detection with getAutoProxyUrlFromEnvironment in the backend. If deployed outside Vercel, auto-proxy simply doesn't activate and you configure proxyUrl manually.

@EphemEphem left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Ephem addressed in 43be86f, replaced header-based detection with getAutoProxyUrlFromEnvironment in the backend. If deployed outside Vercel, auto-proxy simply doesn't activate and you configure proxyUrl manually.

Awesome, that sidesteps the concern entirely, thanks!

I think this is looking good to go now, great work Bryce on the original implementation and Railly on picking it up!

@nikosdouvlis Do you have any final concerns?

@nikosdouvlisnikosdouvlis left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM :)

@Railly

Copy link
Copy Markdown
Contributor

hey @nikosdouvlis, could you merge this when you get a chance? I’m not the PR author so I don’t have permissions

@nikosdouvlis
nikosdouvlis merged commit 9b57986 into mainApr 28, 2026
177 of 192 checks passed
@nikosdouvlis
nikosdouvlis deleted the brkalow/auto-proxy-vercel-subdomains branch April 28, 2026 21:10
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants

@brkalow@dominic-clerk@Railly@nikosdouvlis@Ephem