Skip to content

@cloudflare/vite-plugin: HTTPS/HTTP2 Host/:authority handling drops non-default port from request.url (breaks Clerk handshake redirects) #14931

Description

@intrdx

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:

  1. x-forwarded-host / x-forwarded-proto when present
  2. otherwise request.url / Host
    In @cloudflare/vite-plugin, request adaptation currently:
  3. Builds the Fetch Request in createRequest from Node req headers
  4. Skips HTTP/2 pseudo-headers starting with : (including :authority)
  5. Falls back host to "localhost" when Host is missing
  6. 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:

  1. Prefer explicit option host
  2. Then req.headers.host
  3. Then req.headers[':authority'] (HTTP/2)
  4. Then parsed headers host
  5. 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(),
    // ...
  ],
});

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    package:vite-pluginRelating to the `@cloudflare/vite-plugin` packagequick-winPotentially easy/straightforward issue to tackle

    Type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions