Skip to content

Dev server breaks behind reverse proxy since v1.167.17 (virtual module \0-prefix in browser URL) #7155

@TimElschner

Description

@TimElschner

Description

Since @tanstack/react-start@1.167.17 (PR #7144), the dev server breaks when accessed through a reverse proxy (Cloudflare, nginx, Caddy, Pangolin). The browser receives 400 Bad Request for the new #tanstack-start-plugin-adapters virtual module.

Reproduction

  1. Set up a TanStack Start app with @tanstack/react-start@1.167.17+
  2. Access the dev server through any reverse proxy (e.g., Cloudflare Tunnel, nginx, Caddy)
  3. The app fails to load

Works: Direct access via localhost:3000
Breaks: Access through reverse proxy → https://myapp.example.com

Browser console errors

GET https://myapp.example.com/@id/__x00__%23tanstack-start-plugin-adapters net::ERR_ABORTED 400 (Bad Request)

Uncaught (in promise) TypeError: Failed to fetch dynamically imported module:
  https://myapp.example.com/@id/virtual:tanstack-start-client-entry

Root Cause

PR #7144 introduced a new virtual module #tanstack-start-plugin-adapters in packages/start-plugin-core/src/vite/serialization-adapters-plugin.ts. This module is:

  1. Resolved via resolveId/load hooks (producing a \0-prefixed ID)
  2. Imported by client-side code in hydrateStart.ts

Vite encodes the \0 prefix as __x00__ in browser-facing URLs, resulting in:

/@id/__x00__%23tanstack-start-plugin-adapters

Reverse proxies and WAFs (Cloudflare, nginx, Caddy) correctly reject URLs containing null-byte representations as a security measure (HTTP anomaly detection).

Why other virtual modules work

The existing virtual modules (#tanstack-start-entry, #tanstack-router-entry) use resolve.alias in the Vite config, mapping to real filesystem paths. They never become \0-prefixed, so no /@id/__x00__ URL appears in the browser.

#tanstack-start-server-fn-resolver does use resolveId/load, but it's only loaded server-side (SSR), never requested by the browser.

#tanstack-start-plugin-adapters is the first virtual module that uses resolveId/load AND is imported by client-side code.

Suggested Fix

Handle #tanstack-start-plugin-adapters the same way as #tanstack-start-entry — via resolve.alias pointing to a real file (e.g., packages/start-client-core/src/fake-entries/plugin-adapters.ts), instead of the resolveId/load virtual module pattern.

Environment

  • @tanstack/react-start: 1.167.28 (broken), 1.167.16 (last working)
  • Vite: 7.3.2
  • Reverse proxy: Pangolin (Caddy-based) + Cloudflare DNS proxy
  • OS: Windows 11 (dev), Linux (proxy)
  • Browser: Chrome

Workaround

Pin @tanstack/react-start to 1.167.16 and @tanstack/react-router to 1.168.10.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions