What versions & operating system are you using?
- OS: Windows 11
- Node: v24.18.1
- Package manager: pnpm 11.17.0
@cloudflare/vite-plugin: 1.45.1
wrangler: 4.112.0
vite / Vite+: 8.1.5 / vite-plus 0.2.6
- Framework: TanStack Start +
@clerk/tanstack-react-start 1.4.20
Please provide a link to a minimal reproduction
- Vite HTTPS + @cloudflare/vite-plugin + Clerk middleware below reproduces
Describe the Bug
When the Vite + @cloudflare/vite-plugin dev server is served over HTTPS, Clerk handshake redirects lose the non-default port (:5173) and can loop, e.g.:
- Expected:
https://localhost:5173/...
- Actual:
https://localhost/... (port dropped)
This only shows up under HTTPS. Plain HTTP on localhost:5173 works.
Why this is plugin-side
Clerk builds handshake redirect_url from clerkUrl, which comes from @clerk/backend's deriveUrlFromHeaders() using:
x-forwarded-host / x-forwarded-proto when present
- otherwise
request.url / Host
In @cloudflare/vite-plugin, request adaptation currently:
- Builds the Fetch
Request in createRequest from Node req headers
- Skips HTTP/2 pseudo-headers starting with
: (including :authority)
- Falls back host to
"localhost" when Host is missing
- Sets
X-Forwarded-Host from that host in toMiniflareRequest
On HTTPS, browsers often use HTTP/2, where authority is in :authority rather than a classic Host header. If Host is absent/incomplete and :authority is ignored, the reconstructed origin can become https://localhost without :5173. Clerk then redirects using that broken origin.
This looks like a remaining gap after prior host/protocol fixes:
Expected behavior
With Vite listening on https://localhost:5173 (and/or https://<LAN-IP>:5173):
- Worker
request.url origin includes host and port (:5173)
X-Forwarded-Host preserves host and port
- Auth libraries (Clerk) that reconstruct redirects from request URL / forwarded headers keep
:5173
Actual behavior
Under HTTPS:
- Handshake / redirect URLs become
https://localhost/ (no :5173)
- Clerk
__clerk_handshake / dev-browser-missing redirect chain loops on the wrong origin
Under HTTP:
- Same app / Clerk setup works on
http://localhost:5173
Suspected code path
In @cloudflare/vite-plugin request adaptation:
createRequest(...) host resolution:
- prefers
Host
- ignores
:authority (pseudo-header skipped in header copy)
- falls back to
"localhost" (no port)
toMiniflareRequest(...) then sets X-Forwarded-Host from that host
Suggested fix
When constructing the request URL / forwarded host in the Vite plugin:
- Prefer explicit option host
- Then
req.headers.host
- Then
req.headers[':authority'] (HTTP/2)
- Then parsed headers host
- Only then fallback to localhost
Also ensure the resolved host (including non-default port) is present before setting X-Forwarded-Host.
Additional context
This is especially painful for local mobile/LAN testing where HTTPS is required (secure context / LiveKit, etc.), and Cloudflare docs/issues have previously recommended HTTPS for LAN/dev cases. But this bug blocks local development with HTTPS Vite dev server.
src/start.ts:
import { clerkMiddleware } from '@clerk/tanstack-react-start/server';
import { createCsrfMiddleware, createStart } from '@tanstack/react-start';
const csrfMiddleware = createCsrfMiddleware({
filter: (ctx) => ctx.handlerType === 'serverFn',
});
export const startInstance = createStart(() => ({
requestMiddleware: [csrfMiddleware, clerkMiddleware()],
}));
vite.config.ts:
import fs from 'node:fs';
import { cloudflare } from '@cloudflare/vite-plugin';
import { tanstackStart } from '@tanstack/react-start/plugin/vite';
import { defineConfig } from 'vite-plus'; // or 'vite'
export default defineConfig({
server: {
https: {
key: fs.readFileSync('.certs/dev-key.pem'),
cert: fs.readFileSync('.certs/dev.pem'),
},
},
plugins: [
cloudflare({ viteEnvironment: { name: 'ssr' } }),
tanstackStart(),
// ...
],
});
What versions & operating system are you using?
@cloudflare/vite-plugin: 1.45.1wrangler: 4.112.0vite/ Vite+: 8.1.5 / vite-plus 0.2.6@clerk/tanstack-react-start1.4.20Please provide a link to a minimal reproduction
Describe the Bug
When the Vite +
@cloudflare/vite-plugindev server is served over HTTPS, Clerk handshake redirects lose the non-default port (:5173) and can loop, e.g.:https://localhost:5173/...https://localhost/...(port dropped)This only shows up under HTTPS. Plain HTTP on
localhost:5173works.Why this is plugin-side
Clerk builds handshake
redirect_urlfromclerkUrl, which comes from@clerk/backend'sderiveUrlFromHeaders()using:x-forwarded-host/x-forwarded-protowhen presentrequest.url/HostIn
@cloudflare/vite-plugin, request adaptation currently:RequestincreateRequestfrom Node req headers:(including:authority)"localhost"whenHostis missingX-Forwarded-Hostfrom that host intoMiniflareRequestOn HTTPS, browsers often use HTTP/2, where authority is in
:authorityrather than a classicHostheader. IfHostis absent/incomplete and:authorityis ignored, the reconstructed origin can becomehttps://localhostwithout:5173. Clerk then redirects using that broken origin.This looks like a remaining gap after prior host/protocol fixes:
X-Forwarded-Hostfor Clerk wrong-host redirects)request.urlis alwayshttp://…behind a TLS-terminating proxy —dev.upstream_protocolandX-Forwarded-Protoboth ignored #13801 / [vite-plugin-cloudflare] Honor X-Forwarded-Proto when constructing request.url #13920 (X-Forwarded-Protoforrequest.url)Those address host/protocol forwarding, but not this HTTPS/HTTP2 port/authority reconstruction case.
Expected behavior
With Vite listening on
https://localhost:5173(and/orhttps://<LAN-IP>:5173):request.urlorigin includes host and port (:5173)X-Forwarded-Hostpreserves host and port:5173Actual behavior
Under HTTPS:
https://localhost/(no:5173)__clerk_handshake/dev-browser-missingredirect chain loops on the wrong originUnder HTTP:
http://localhost:5173Suspected code path
In
@cloudflare/vite-pluginrequest adaptation:createRequest(...)host resolution:Host:authority(pseudo-header skipped in header copy)"localhost"(no port)toMiniflareRequest(...)then setsX-Forwarded-Hostfrom that hostSuggested fix
When constructing the request URL / forwarded host in the Vite plugin:
req.headers.hostreq.headers[':authority'](HTTP/2)Also ensure the resolved host (including non-default port) is present before setting
X-Forwarded-Host.Additional context
This is especially painful for local mobile/LAN testing where HTTPS is required (secure context / LiveKit, etc.), and Cloudflare docs/issues have previously recommended HTTPS for LAN/dev cases. But this bug blocks local development with HTTPS Vite dev server.
src/start.ts:vite.config.ts: